diff options
Diffstat (limited to 'src')
34 files changed, 2196 insertions, 128 deletions
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 87d7386e013..89a0221ec9b 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -38,6 +38,7 @@ OBJS = \ pg_largeobject.o \ pg_namespace.o \ pg_operator.o \ + pg_parameter_acl.o \ pg_proc.o \ pg_publication.o \ pg_range.o \ @@ -68,7 +69,8 @@ CATALOG_HEADERS := \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h pg_policy.h pg_replication_origin.h \ pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \ - pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \ + pg_collation.h pg_parameter_acl.h pg_partitioned_table.h \ + pg_range.h pg_transform.h \ pg_sequence.h pg_publication.h pg_publication_namespace.h \ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 1dd03a8e516..5f1726c0957 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -48,6 +48,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_parameter_acl.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_subscription.h" @@ -112,11 +113,13 @@ static void ExecGrant_Largeobject(InternalGrant *grantStmt); static void ExecGrant_Namespace(InternalGrant *grantStmt); static void ExecGrant_Tablespace(InternalGrant *grantStmt); static void ExecGrant_Type(InternalGrant *grantStmt); +static void ExecGrant_Parameter(InternalGrant *grantStmt); static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames); static void SetDefaultACL(InternalDefaultACL *iacls); -static List *objectNamesToOids(ObjectType objtype, List *objnames); +static List *objectNamesToOids(ObjectType objtype, List *objnames, + bool is_grant); static List *objectsInSchemaToOids(ObjectType objtype, List *nspnames); static List *getRelationsInNamespace(Oid namespaceId, char relkind); static void expand_col_privileges(List *colnames, Oid table_oid, @@ -259,6 +262,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, case OBJECT_TYPE: whole_mask = ACL_ALL_RIGHTS_TYPE; break; + case OBJECT_PARAMETER_ACL: + whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL; + break; default: elog(ERROR, "unrecognized object type: %d", objtype); /* not reached, but keep compiler quiet */ @@ -390,7 +396,8 @@ ExecuteGrantStmt(GrantStmt *stmt) switch (stmt->targtype) { case ACL_TARGET_OBJECT: - istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects); + istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects, + stmt->is_grant); break; case ACL_TARGET_ALL_IN_SCHEMA: istmt.objects = objectsInSchemaToOids(stmt->objtype, stmt->objects); @@ -498,6 +505,10 @@ ExecuteGrantStmt(GrantStmt *stmt) all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER; errormsg = gettext_noop("invalid privilege type %s for foreign server"); break; + case OBJECT_PARAMETER_ACL: + all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL; + errormsg = gettext_noop("invalid privilege type %s for parameter"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) stmt->objtype); @@ -600,6 +611,9 @@ ExecGrantStmt_oids(InternalGrant *istmt) case OBJECT_TABLESPACE: ExecGrant_Tablespace(istmt); break; + case OBJECT_PARAMETER_ACL: + ExecGrant_Parameter(istmt); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) istmt->objtype); @@ -626,7 +640,7 @@ ExecGrantStmt_oids(InternalGrant *istmt) * to fail. */ static List * -objectNamesToOids(ObjectType objtype, List *objnames) +objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant) { List *objects = NIL; ListCell *cell; @@ -759,6 +773,37 @@ objectNamesToOids(ObjectType objtype, List *objnames) objects = lappend_oid(objects, srvid); } break; + case OBJECT_PARAMETER_ACL: + foreach(cell, objnames) + { + /* + * In this code we represent a GUC by the OID of its entry in + * pg_parameter_acl, which we have to manufacture here if it + * doesn't exist yet. (That's a hack for sure, but it avoids + * messing with all the GRANT/REVOKE infrastructure that + * expects to use OIDs for object identities.) However, if + * this is a REVOKE, we can instead just ignore any GUCs that + * don't have such an entry, as they must not have any + * privileges needing removal. + */ + char *parameter = strVal(lfirst(cell)); + Oid parameterId = ParameterAclLookup(parameter, true); + + if (!OidIsValid(parameterId) && is_grant) + { + parameterId = ParameterAclCreate(parameter); + + /* + * Prevent error when processing duplicate objects, and + * make this new entry visible so that ExecGrant_Parameter + * can update it. + */ + CommandCounterIncrement(); + } + if (OidIsValid(parameterId)) + objects = lappend_oid(objects, parameterId); + } + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) objtype); @@ -1494,6 +1539,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) case ForeignDataWrapperRelationId: istmt.objtype = OBJECT_FDW; break; + case ParameterAclRelationId: + istmt.objtype = OBJECT_PARAMETER_ACL; + break; default: elog(ERROR, "unexpected object class %u", classid); break; @@ -3225,6 +3273,154 @@ ExecGrant_Type(InternalGrant *istmt) table_close(relation, RowExclusiveLock); } +static void +ExecGrant_Parameter(InternalGrant *istmt) +{ + Relation relation; + ListCell *cell; + + if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS) + istmt->privileges = ACL_ALL_RIGHTS_PARAMETER_ACL; + + relation = table_open(ParameterAclRelationId, RowExclusiveLock); + + foreach(cell, istmt->objects) + { + Oid parameterId = lfirst_oid(cell); + Datum nameDatum; + const char *parname; + Datum aclDatum; + bool isNull; + AclMode avail_goptions; + AclMode this_privileges; + Acl *old_acl; + Acl *new_acl; + Oid grantorId; + Oid ownerId; + HeapTuple tuple; + int noldmembers; + int nnewmembers; + Oid *oldmembers; + Oid *newmembers; + + tuple = SearchSysCache1(PARAMETERACLOID, ObjectIdGetDatum(parameterId)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for parameter ACL %u", + parameterId); + + /* We'll need the GUC's name */ + nameDatum = SysCacheGetAttr(PARAMETERACLOID, tuple, + Anum_pg_parameter_acl_parname, + &isNull); + Assert(!isNull); + parname = TextDatumGetCString(nameDatum); + + /* Treat all parameters as belonging to the bootstrap superuser. */ + ownerId = BOOTSTRAP_SUPERUSERID; + + /* + * Get working copy of existing ACL. If there's no ACL, substitute the + * proper default. + */ + aclDatum = SysCacheGetAttr(PARAMETERACLOID, tuple, + Anum_pg_parameter_acl_paracl, + &isNull); + + if (isNull) + { + old_acl = acldefault(istmt->objtype, ownerId); + /* There are no old member roles according to the catalogs */ + noldmembers = 0; + oldmembers = NULL; + } + else + { + old_acl = DatumGetAclPCopy(aclDatum); + /* Get the roles mentioned in the existing ACL */ + noldmembers = aclmembers(old_acl, &oldmembers); + } + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), istmt->privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); + + /* + * Restrict the privileges to what we can actually grant, and emit the + * standards-mandated warning and error messages. + */ + this_privileges = + restrict_and_check_grant(istmt->is_grant, avail_goptions, + istmt->all_privs, istmt->privileges, + parameterId, grantorId, + OBJECT_PARAMETER_ACL, + parname, + 0, NULL); + + /* + * Generate new ACL. + */ + new_acl = merge_acl_with_grant(old_acl, istmt->is_grant, + istmt->grant_option, istmt->behavior, + istmt->grantees, this_privileges, + grantorId, ownerId); + + /* + * We need the members of both old and new ACLs so we can correct the + * shared dependency information. + */ + nnewmembers = aclmembers(new_acl, &newmembers); + + /* + * If the new ACL is equal to the default, we don't need the catalog + * entry any longer. Delete it rather than updating it, to avoid + * leaving a degenerate entry. + */ + if (aclequal(new_acl, acldefault(istmt->objtype, ownerId))) + { + CatalogTupleDelete(relation, &tuple->t_self); + } + else + { + /* finished building new ACL value, now insert it */ + HeapTuple newtuple; + Datum values[Natts_pg_parameter_acl]; + bool nulls[Natts_pg_parameter_acl]; + bool replaces[Natts_pg_parameter_acl]; + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); + + replaces[Anum_pg_parameter_acl_paracl - 1] = true; + values[Anum_pg_parameter_acl_paracl - 1] = PointerGetDatum(new_acl); + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), + values, nulls, replaces); + + CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); + } + + /* Update initial privileges for extensions */ + recordExtensionInitPriv(parameterId, ParameterAclRelationId, 0, + new_acl); + + /* Update the shared dependency ACL info */ + updateAclDependencies(ParameterAclRelationId, parameterId, 0, + ownerId, + noldmembers, oldmembers, + nnewmembers, newmembers); + + ReleaseSysCache(tuple); + pfree(new_acl); + + /* prevent error when processing duplicate objects */ + CommandCounterIncrement(); + } + + table_close(relation, RowExclusiveLock); +} + static AclMode string_to_privilege(const char *privname) @@ -3255,6 +3451,10 @@ string_to_privilege(const char *privname) return ACL_CREATE_TEMP; if (strcmp(privname, "connect") == 0) return ACL_CONNECT; + if (strcmp(privname, "set") == 0) + return ACL_SET; + if (strcmp(privname, "alter system") == 0) + return ACL_ALTER_SYSTEM; if (strcmp(privname, "rule") == 0) return 0; /* ignore old RULE privileges */ ereport(ERROR, @@ -3292,6 +3492,10 @@ privilege_to_string(AclMode privilege) return "TEMP"; case ACL_CONNECT: return "CONNECT"; + case ACL_SET: + return "SET"; + case ACL_ALTER_SYSTEM: + return "ALTER SYSTEM"; default: elog(ERROR, "unrecognized privilege: %d", (int) privilege); } @@ -3376,6 +3580,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_OPFAMILY: msg = gettext_noop("permission denied for operator family %s"); break; + case OBJECT_PARAMETER_ACL: + msg = gettext_noop("permission denied for parameter %s"); + break; case OBJECT_POLICY: msg = gettext_noop("permission denied for policy %s"); break; @@ -3567,6 +3774,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_DEFAULT: case OBJECT_DEFACL: case OBJECT_DOMCONSTRAINT: + case OBJECT_PARAMETER_ACL: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: case OBJECT_ROLE: @@ -3653,6 +3861,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid, case OBJECT_LARGEOBJECT: return pg_largeobject_aclmask_snapshot(table_oid, roleid, mask, how, NULL); + case OBJECT_PARAMETER_ACL: + return pg_parameter_acl_aclmask(table_oid, roleid, mask, how); case OBJECT_SCHEMA: return pg_namespace_aclmask(table_oid, roleid, mask, how); case OBJECT_STATISTIC_EXT: @@ -4001,6 +4211,121 @@ pg_database_aclmask(Oid db_oid, Oid roleid, } /* + * Exported routine for examining a user's privileges for a configuration + * parameter (GUC), identified by GUC name. + */ +AclMode +pg_parameter_aclmask(const char *name, Oid roleid, AclMode mask, AclMaskHow how) +{ + AclMode result; + char *parname; + text *partext; + HeapTuple tuple; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return mask; + + /* Convert name to the form it should have in pg_parameter_acl... */ + parname = convert_GUC_name_for_parameter_acl(name); + partext = cstring_to_text(parname); + + /* ... and look it up */ + tuple = SearchSysCache1(PARAMETERACLNAME, PointerGetDatum(partext)); + + if (!HeapTupleIsValid(tuple)) + { + /* If no entry, GUC has no permissions for non-superusers */ + result = ACL_NO_RIGHTS; + } + else + { + Datum aclDatum; + bool isNull; + Acl *acl; + + aclDatum = SysCacheGetAttr(PARAMETERACLNAME, tuple, + Anum_pg_parameter_acl_paracl, + &isNull); + if (isNull) + { + /* No ACL, so build default ACL */ + acl = acldefault(OBJECT_PARAMETER_ACL, BOOTSTRAP_SUPERUSERID); + aclDatum = (Datum) 0; + } + else + { + /* detoast ACL if necessary */ + acl = DatumGetAclP(aclDatum); + } + + result = aclmask(acl, roleid, BOOTSTRAP_SUPERUSERID, mask, how); + + /* if we have a detoasted copy, free it */ + if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) + pfree(acl); + + ReleaseSysCache(tuple); + } + + pfree(parname); + pfree(partext); + + return result; +} + +/* + * Exported routine for examining a user's privileges for a configuration + * parameter (GUC), identified by the OID of its pg_parameter_acl entry. + */ +AclMode +pg_parameter_acl_aclmask(Oid acl_oid, Oid roleid, AclMode mask, AclMaskHow how) +{ + AclMode result; + HeapTuple tuple; + Datum aclDatum; + bool isNull; + Acl *acl; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return mask; + + /* Get the ACL from pg_parameter_acl */ + tuple = SearchSysCache1(PARAMETERACLOID, ObjectIdGetDatum(acl_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("parameter ACL with OID %u does not exist", + acl_oid))); + + aclDatum = SysCacheGetAttr(PARAMETERACLOID, tuple, + Anum_pg_parameter_acl_paracl, + &isNull); + if (isNull) + { + /* No ACL, so build default ACL */ + acl = acldefault(OBJECT_PARAMETER_ACL, BOOTSTRAP_SUPERUSERID); + aclDatum = (Datum) 0; + } + else + { + /* detoast ACL if necessary */ + acl = DatumGetAclP(aclDatum); + } + + result = aclmask(acl, roleid, BOOTSTRAP_SUPERUSERID, mask, how); + + /* if we have a detoasted copy, free it */ + if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) + pfree(acl); + + ReleaseSysCache(tuple); + + return result; +} + +/* * Exported routine for examining a user's privileges for a function */ AclMode @@ -4714,6 +5039,32 @@ pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode) } /* + * Exported routine for checking a user's access privileges to a configuration + * parameter (GUC), identified by GUC name. + */ +AclResult +pg_parameter_aclcheck(const char *name, Oid roleid, AclMode mode) +{ + if (pg_parameter_aclmask(name, roleid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + +/* + * Exported routine for checking a user's access privileges to a configuration + * parameter (GUC), identified by the OID of its pg_parameter_acl entry. + */ +AclResult +pg_parameter_acl_aclcheck(Oid acl_oid, Oid roleid, AclMode mode) +{ + if (pg_parameter_acl_aclmask(acl_oid, roleid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + +/* * Exported routine for checking a user's access privileges to a function */ AclResult diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index dfd5fb669ee..520f77971b3 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -33,6 +33,7 @@ #include "catalog/pg_db_role_setting.h" #include "catalog/pg_largeobject.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_parameter_acl.h" #include "catalog/pg_replication_origin.h" #include "catalog/pg_shdepend.h" #include "catalog/pg_shdescription.h" @@ -247,32 +248,35 @@ IsSharedRelation(Oid relationId) if (relationId == AuthIdRelationId || relationId == AuthMemRelationId || relationId == DatabaseRelationId || - relationId == SharedDescriptionRelationId || - relationId == SharedDependRelationId || - relationId == SharedSecLabelRelationId || - relationId == TableSpaceRelationId || relationId == DbRoleSettingRelationId || + relationId == ParameterAclRelationId || relationId == ReplicationOriginRelationId || - relationId == SubscriptionRelationId) + relationId == SharedDependRelationId || + relationId == SharedDescriptionRelationId || + relationId == SharedSecLabelRelationId || + relationId == SubscriptionRelationId || + relationId == TableSpaceRelationId) return true; /* These are their indexes */ - if (relationId == AuthIdRolnameIndexId || - relationId == AuthIdOidIndexId || - relationId == AuthMemRoleMemIndexId || + if (relationId == AuthIdOidIndexId || + relationId == AuthIdRolnameIndexId || relationId == AuthMemMemRoleIndexId || + relationId == AuthMemRoleMemIndexId || relationId == DatabaseNameIndexId || relationId == DatabaseOidIndexId || - relationId == SharedDescriptionObjIndexId || - relationId == SharedDependDependerIndexId || - relationId == SharedDependReferenceIndexId || - relationId == SharedSecLabelObjectIndexId || - relationId == TablespaceOidIndexId || - relationId == TablespaceNameIndexId || relationId == DbRoleSettingDatidRolidIndexId || + relationId == ParameterAclOidIndexId || + relationId == ParameterAclParnameIndexId || relationId == ReplicationOriginIdentIndex || relationId == ReplicationOriginNameIndex || + relationId == SharedDependDependerIndexId || + relationId == SharedDependReferenceIndexId || + relationId == SharedDescriptionObjIndexId || + relationId == SharedSecLabelObjectIndexId || + relationId == SubscriptionNameIndexId || relationId == SubscriptionObjectIndexId || - relationId == SubscriptionNameIndexId) + relationId == TablespaceNameIndexId || + relationId == TablespaceOidIndexId) return true; /* These are their toast tables and toast indexes */ if (relationId == PgAuthidToastTable || @@ -281,6 +285,8 @@ IsSharedRelation(Oid relationId) relationId == PgDatabaseToastIndex || relationId == PgDbRoleSettingToastTable || relationId == PgDbRoleSettingToastIndex || + relationId == PgParameterAclToastTable || + relationId == PgParameterAclToastIndex || relationId == PgReplicationOriginToastTable || relationId == PgReplicationOriginToastIndex || relationId == PgShdescriptionToastTable || diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 25fe56d3101..de109233910 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -46,6 +46,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_parameter_acl.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" #include "catalog/pg_publication.h" @@ -178,6 +179,7 @@ static const Oid object_classes[] = { DefaultAclRelationId, /* OCLASS_DEFACL */ ExtensionRelationId, /* OCLASS_EXTENSION */ EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */ + ParameterAclRelationId, /* OCLASS_PARAMETER_ACL */ PolicyRelationId, /* OCLASS_POLICY */ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */ PublicationRelationId, /* OCLASS_PUBLICATION */ @@ -1507,6 +1509,7 @@ doDeletion(const ObjectAddress *object, int flags) case OCLASS_DATABASE: case OCLASS_TBLSPACE: case OCLASS_SUBSCRIPTION: + case OCLASS_PARAMETER_ACL: elog(ERROR, "global objects cannot be deleted by doDeletion"); break; @@ -2861,6 +2864,9 @@ getObjectClass(const ObjectAddress *object) case EventTriggerRelationId: return OCLASS_EVENT_TRIGGER; + case ParameterAclRelationId: + return OCLASS_PARAMETER_ACL; + case PolicyRelationId: return OCLASS_POLICY; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 3fd17ea64f0..31c80f7209f 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -45,6 +45,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_parameter_acl.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" #include "catalog/pg_publication.h" @@ -818,6 +819,10 @@ static const struct object_type_map { "event trigger", OBJECT_EVENT_TRIGGER }, + /* OCLASS_PARAMETER_ACL */ + { + "parameter ACL", OBJECT_PARAMETER_ACL + }, /* OCLASS_POLICY */ { "policy", OBJECT_POLICY @@ -1014,6 +1019,7 @@ get_object_address(ObjectType objtype, Node *object, case OBJECT_FDW: case OBJECT_FOREIGN_SERVER: case OBJECT_EVENT_TRIGGER: + case OBJECT_PARAMETER_ACL: case OBJECT_ACCESS_METHOD: case OBJECT_PUBLICATION: case OBJECT_SUBSCRIPTION: @@ -1315,6 +1321,11 @@ get_object_address_unqualified(ObjectType objtype, address.objectId = get_event_trigger_oid(name, missing_ok); address.objectSubId = 0; break; + case OBJECT_PARAMETER_ACL: + address.classId = ParameterAclRelationId; + address.objectId = ParameterAclLookup(name, missing_ok); + address.objectSubId = 0; + break; case OBJECT_PUBLICATION: address.classId = PublicationRelationId; address.objectId = get_publication_oid(name, missing_ok); @@ -2307,6 +2318,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_FDW: case OBJECT_FOREIGN_SERVER: case OBJECT_LANGUAGE: + case OBJECT_PARAMETER_ACL: case OBJECT_PUBLICATION: case OBJECT_ROLE: case OBJECT_SCHEMA: @@ -2597,6 +2609,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: case OBJECT_ACCESS_METHOD: + case OBJECT_PARAMETER_ACL: /* We treat these object types as being owned by superusers */ if (!superuser_arg(roleid)) ereport(ERROR, @@ -3880,6 +3893,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case OCLASS_PARAMETER_ACL: + { + HeapTuple tup; + Datum nameDatum; + bool isNull; + char *parname; + + tup = SearchSysCache1(PARAMETERACLOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for parameter ACL %u", + object->objectId); + break; + } + nameDatum = SysCacheGetAttr(PARAMETERACLOID, tup, + Anum_pg_parameter_acl_parname, + &isNull); + Assert(!isNull); + parname = TextDatumGetCString(nameDatum); + appendStringInfo(&buffer, _("parameter %s"), parname); + ReleaseSysCache(tup); + break; + } + case OCLASS_POLICY: { Relation policy_rel; @@ -4547,6 +4586,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) appendStringInfoString(&buffer, "event trigger"); break; + case OCLASS_PARAMETER_ACL: + appendStringInfoString(&buffer, "parameter ACL"); + break; + case OCLASS_POLICY: appendStringInfoString(&buffer, "policy"); break; @@ -5693,6 +5736,34 @@ getObjectIdentityParts(const ObjectAddress *object, break; } + case OCLASS_PARAMETER_ACL: + { + HeapTuple tup; + Datum nameDatum; + bool isNull; + char *parname; + + tup = SearchSysCache1(PARAMETERACLOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for parameter ACL %u", + object->objectId); + break; + } + nameDatum = SysCacheGetAttr(PARAMETERACLOID, tup, + Anum_pg_parameter_acl_parname, + &isNull); + Assert(!isNull); + parname = TextDatumGetCString(nameDatum); + appendStringInfoString(&buffer, parname); + if (objname) + *objname = list_make1(parname); + ReleaseSysCache(tup); + break; + } + case OCLASS_POLICY: { Relation polDesc; diff --git a/src/backend/catalog/pg_parameter_acl.c b/src/backend/catalog/pg_parameter_acl.c new file mode 100644 index 00000000000..2decee909b3 --- /dev/null +++ b/src/backend/catalog/pg_parameter_acl.c @@ -0,0 +1,118 @@ +/*------------------------------------------------------------------------- + * + * pg_parameter_acl.c + * routines to support manipulation of the pg_parameter_acl relation + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/catalog/pg_parameter_acl.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/table.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_parameter_acl.h" +#include "utils/builtins.h" +#include "utils/pg_locale.h" +#include "utils/rel.h" +#include "utils/syscache.h" + + +/* + * ParameterAclLookup - Given a configuration parameter name, + * look up the associated configuration parameter ACL's OID. + * + * If missing_ok is false, throw an error if ACL entry not found. If + * true, just return InvalidOid. + */ +Oid +ParameterAclLookup(const char *parameter, bool missing_ok) +{ + Oid oid; + char *parname; + + /* Convert name to the form it should have in pg_parameter_acl... */ + parname = convert_GUC_name_for_parameter_acl(parameter); + + /* ... and look it up */ + oid = GetSysCacheOid1(PARAMETERACLNAME, Anum_pg_parameter_acl_oid, + PointerGetDatum(cstring_to_text(parname))); + + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("parameter ACL \"%s\" does not exist", parameter))); + + pfree(parname); + + return oid; +} + +/* + * ParameterAclCreate + * + * Add a new tuple to pg_parameter_acl. + * + * parameter: the parameter name to create an entry for. + * Caller should have verified that there's no such entry already. + * + * Returns the new entry's OID. + */ +Oid +ParameterAclCreate(const char *parameter) +{ + Oid parameterId; + char *parname; + Relation rel; + TupleDesc tupDesc; + HeapTuple tuple; + Datum values[Natts_pg_parameter_acl]; + bool nulls[Natts_pg_parameter_acl]; + + /* + * To prevent cluttering pg_parameter_acl with useless entries, insist + * that the name be valid. + */ + if (!check_GUC_name_for_parameter_acl(parameter)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid parameter name \"%s\"", + parameter))); + + /* Convert name to the form it should have in pg_parameter_acl. */ + parname = convert_GUC_name_for_parameter_acl(parameter); + + /* + * Create and insert a new record containing a null ACL. + * + * We don't take a strong enough lock to prevent concurrent insertions, + * relying instead on the unique index. + */ + rel = table_open(ParameterAclRelationId, RowExclusiveLock); + tupDesc = RelationGetDescr(rel); + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + parameterId = GetNewOidWithIndex(rel, + ParameterAclOidIndexId, + Anum_pg_parameter_acl_oid); + values[Anum_pg_parameter_acl_oid - 1] = ObjectIdGetDatum(parameterId); + values[Anum_pg_parameter_acl_parname - 1] = + PointerGetDatum(cstring_to_text(parname)); + nulls[Anum_pg_parameter_acl_paracl - 1] = true; + tuple = heap_form_tuple(tupDesc, values, nulls); + CatalogTupleInsert(rel, tuple); + + /* Close pg_parameter_acl, but keep lock till commit. */ + heap_freetuple(tuple); + table_close(rel, NoLock); + + return parameterId; +} diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 1f64c8aa517..5456b8222ba 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -658,6 +658,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, case OCLASS_DEFACL: case OCLASS_EXTENSION: case OCLASS_EVENT_TRIGGER: + case OCLASS_PARAMETER_ACL: case OCLASS_POLICY: case OCLASS_PUBLICATION: case OCLASS_PUBLICATION_NAMESPACE: diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 3c3fc2515b7..46425278811 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -940,6 +940,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_DATABASE: case OBJECT_TABLESPACE: case OBJECT_ROLE: + case OBJECT_PARAMETER_ACL: /* no support for global objects */ return false; case OBJECT_EVENT_TRIGGER: @@ -1015,6 +1016,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_DATABASE: case OCLASS_TBLSPACE: case OCLASS_ROLE: + case OCLASS_PARAMETER_ACL: /* no support for global objects */ return false; case OCLASS_EVENT_TRIGGER: @@ -2042,6 +2044,8 @@ stringify_grant_objtype(ObjectType objtype) return "LARGE OBJECT"; case OBJECT_SCHEMA: return "SCHEMA"; + case OBJECT_PARAMETER_ACL: + return "PARAMETER"; case OBJECT_PROCEDURE: return "PROCEDURE"; case OBJECT_ROUTINE: @@ -2153,6 +2157,7 @@ stringify_adefprivs_objtype(ObjectType objtype) case OBJECT_OPCLASS: case OBJECT_OPERATOR: case OBJECT_OPFAMILY: + case OBJECT_PARAMETER_ACL: case OBJECT_POLICY: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_NAMESPACE: diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 7a62d547e2f..7ae19b98bb9 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -78,6 +78,7 @@ SecLabelSupportsObjectType(ObjectType objtype) case OBJECT_OPCLASS: case OBJECT_OPERATOR: case OBJECT_OPFAMILY: + case OBJECT_PARAMETER_ACL: case OBJECT_POLICY: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 7febb5018fc..a241b444975 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -12655,6 +12655,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, case OCLASS_DEFACL: case OCLASS_EXTENSION: case OCLASS_EVENT_TRIGGER: + case OCLASS_PARAMETER_ACL: case OCLASS_PUBLICATION: case OCLASS_PUBLICATION_NAMESPACE: case OCLASS_PUBLICATION_REL: diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 7e3f4a5d275..2cc92a89432 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -371,8 +371,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <str> foreign_server_version opt_foreign_server_version %type <str> opt_in_database -%type <str> OptSchemaName -%type <list> OptSchemaEltList +%type <str> OptSchemaName parameter_name +%type <list> OptSchemaEltList parameter_name_list %type <chr> am_type @@ -827,7 +827,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLAN PLANS POLICY + PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH + PLACING PLAN PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION @@ -7197,6 +7198,13 @@ privilege: SELECT opt_column_list n->cols = $2; $$ = n; } + | ALTER SYSTEM_P + { + AccessPriv *n = makeNode(AccessPriv); + n->priv_name = pstrdup("alter system"); + n->cols = NIL; + $$ = n; + } | ColId opt_column_list { AccessPriv *n = makeNode(AccessPriv); @@ -7206,6 +7214,28 @@ privilege: SELECT opt_column_list } ; +parameter_name_list: + parameter_name + { + $$ = list_make1(makeString($1)); + } + | parameter_name_list ',' parameter_name + { + $$ = lappend($1, makeString($3)); + } + ; + +parameter_name: + ColId + { + $$ = $1; + } + | parameter_name '.' ColId + { + $$ = psprintf("%s.%s", $1, $3); + } + ; + /* Don't bother trying to fold the first two rules into one using * opt_table. You're going to get conflicts. @@ -7307,6 +7337,14 @@ privilege_target: n->objs = $3; $$ = n; } + | PARAMETER parameter_name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + n->targtype = ACL_TARGET_OBJECT; + n->objtype = OBJECT_PARAMETER_ACL; + n->objs = $2; + $$ = n; + } | SCHEMA name_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); @@ -17065,6 +17103,7 @@ unreserved_keyword: | OWNED | OWNER | PARALLEL + | PARAMETER | PARSER | PARTIAL | PARTITION @@ -17682,6 +17721,7 @@ bare_label_keyword: | OWNED | OWNER | PARALLEL + | PARAMETER | PARSER | PARTIAL | PARTITION diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 83cf7ac9ff9..8bdba1c42a5 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -23,6 +23,7 @@ #include "catalog/pg_authid.h" #include "catalog/pg_class.h" #include "catalog/pg_database.h" +#include "catalog/pg_parameter_acl.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "commands/proclang.h" @@ -36,6 +37,7 @@ #include "utils/array.h" #include "utils/builtins.h" #include "utils/catcache.h" +#include "utils/guc.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -109,6 +111,7 @@ static Oid convert_tablespace_name(text *tablespacename); static AclMode convert_tablespace_priv_string(text *priv_type_text); static Oid convert_type_name(text *typename); static AclMode convert_type_priv_string(text *priv_type_text); +static AclMode convert_parameter_priv_string(text *priv_text); static AclMode convert_role_priv_string(text *priv_type_text); static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode); @@ -306,6 +309,12 @@ aclparse(const char *s, AclItem *aip) case ACL_CONNECT_CHR: read = ACL_CONNECT; break; + case ACL_SET_CHR: + read = ACL_SET; + break; + case ACL_ALTER_SYSTEM_CHR: + read = ACL_ALTER_SYSTEM; + break; case 'R': /* ignore old RULE privileges */ read = 0; break; @@ -794,6 +803,10 @@ acldefault(ObjectType objtype, Oid ownerId) world_default = ACL_USAGE; owner_default = ACL_ALL_RIGHTS_TYPE; break; + case OBJECT_PARAMETER_ACL: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ @@ -876,6 +889,9 @@ acldefault_sql(PG_FUNCTION_ARGS) case 'n': objtype = OBJECT_SCHEMA; break; + case 'p': + objtype = OBJECT_PARAMETER_ACL; + break; case 't': objtype = OBJECT_TABLESPACE; break; @@ -1602,6 +1618,10 @@ convert_priv_string(text *priv_type_text) return ACL_CREATE_TEMP; if (pg_strcasecmp(priv_type, "CONNECT") == 0) return ACL_CONNECT; + if (pg_strcasecmp(priv_type, "SET") == 0) + return ACL_SET; + if (pg_strcasecmp(priv_type, "ALTER SYSTEM") == 0) + return ACL_ALTER_SYSTEM; if (pg_strcasecmp(priv_type, "RULE") == 0) return 0; /* ignore old RULE privileges */ @@ -1698,6 +1718,10 @@ convert_aclright_to_string(int aclright) return "TEMPORARY"; case ACL_CONNECT: return "CONNECT"; + case ACL_SET: + return "SET"; + case ACL_ALTER_SYSTEM: + return "ALTER SYSTEM"; default: elog(ERROR, "unrecognized aclright: %d", aclright); return NULL; @@ -4429,6 +4453,96 @@ convert_type_priv_string(text *priv_type_text) return convert_any_priv_string(priv_type_text, type_priv_map); } +/* + * has_parameter_privilege variants + * These are all named "has_parameter_privilege" at the SQL level. + * They take various combinations of parameter name with + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has been granted + * the indicated privilege or false if not. + */ + +/* + * has_param_priv_byname + * + * Helper function to check user privileges on a parameter given the + * role by Oid, parameter by text name, and privileges as AclMode. + */ +static bool +has_param_priv_byname(Oid roleid, const text *parameter, AclMode priv) +{ + char *paramstr = text_to_cstring(parameter); + + return pg_parameter_aclcheck(paramstr, roleid, priv) == ACLCHECK_OK; +} + +/* + * has_parameter_privilege_name_name + * Check user privileges on a parameter given name username, text + * parameter, and text priv name. + */ +Datum +has_parameter_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *parameter = PG_GETARG_TEXT_PP(1); + AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2)); + Oid roleid = get_role_oid_or_public(NameStr(*username)); + + PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv)); +} + +/* + * has_parameter_privilege_name + * Check user privileges on a parameter given text parameter and text priv + * name. current_user is assumed + */ +Datum +has_parameter_privilege_name(PG_FUNCTION_ARGS) +{ + text *parameter = PG_GETARG_TEXT_PP(0); + AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(1)); + + PG_RETURN_BOOL(has_param_priv_byname(GetUserId(), parameter, priv)); +} + +/* + * has_parameter_privilege_id_name + * Check user privileges on a parameter given roleid, text parameter, and + * text priv name. + */ +Datum +has_parameter_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *parameter = PG_GETARG_TEXT_PP(1); + AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2)); + + PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv)); +} + +/* + * Support routines for has_parameter_privilege family. + */ + +/* + * convert_parameter_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_parameter_priv_string(text *priv_text) +{ + static const priv_map parameter_priv_map[] = { + {"SET", ACL_SET}, + {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET)}, + {"ALTER SYSTEM", ACL_ALTER_SYSTEM}, + {"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_text, parameter_priv_map); +} /* * pg_has_role variants diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index a675877d195..8d265f2d23c 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -47,6 +47,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_parameter_acl.h" #include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" #include "catalog/pg_publication.h" @@ -574,6 +575,28 @@ static const struct cachedesc cacheinfo[] = { }, 8 }, + {ParameterAclRelationId, /* PARAMETERACLNAME */ + ParameterAclParnameIndexId, + 1, + { + Anum_pg_parameter_acl_parname, + 0, + 0, + 0 + }, + 4 + }, + {ParameterAclRelationId, /* PARAMETERACLOID */ + ParameterAclOidIndexId, + 1, + { + Anum_pg_parameter_acl_oid, + 0, + 0, + 0 + }, + 4 + }, {PartitionedRelationId, /* PARTRELID */ PartitionedRelidIndexId, 1, diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 9e8ab1420d9..5538465d7d6 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -45,6 +45,7 @@ #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_authid.h" +#include "catalog/pg_parameter_acl.h" #include "catalog/storage.h" #include "commands/async.h" #include "commands/prepare.h" @@ -5714,6 +5715,65 @@ guc_name_compare(const char *namea, const char *nameb) /* + * Convert a GUC name to the form that should be used in pg_parameter_acl. + * + * We need to canonicalize entries since, for example, case should not be + * significant. In addition, we apply the map_old_guc_names[] mapping so that + * any obsolete names will be converted when stored in a new PG version. + * Note however that this function does not verify legality of the name. + * + * The result is a palloc'd string. + */ +char * +convert_GUC_name_for_parameter_acl(const char *name) +{ + char *result; + + /* Apply old-GUC-name mapping. */ + for (int i = 0; map_old_guc_names[i] != NULL; i += 2) + { + if (guc_name_compare(name, map_old_guc_names[i]) == 0) + { + name = map_old_guc_names[i + 1]; + break; + } + } + + /* Apply case-folding that matches guc_name_compare(). */ + result = pstrdup(name); + for (char *ptr = result; *ptr != '\0'; ptr++) + { + char ch = *ptr; + + if (ch >= 'A' && ch <= 'Z') + { + ch += 'a' - 'A'; + *ptr = ch; + } + } + + return result; +} + +/* + * Check whether we should allow creation of a pg_parameter_acl entry + * for the given name. (This can be applied either before or after + * canonicalizing it.) + */ +bool +check_GUC_name_for_parameter_acl(const char *name) +{ + /* OK if the GUC exists. */ + if (find_option(name, false, true, DEBUG1) != NULL) + return true; + /* Otherwise, it'd better be a valid custom GUC name. */ + if (valid_custom_variable_name(name)) + return true; + return false; +} + + +/* * Initialize GUC options during program startup. * * Note that we cannot read the config file yet, since we have not yet @@ -7518,14 +7578,24 @@ set_config_option(const char *name, const char *value, */ break; case PGC_SU_BACKEND: - /* Reject if we're connecting but user is not superuser */ if (context == PGC_BACKEND) { - ereport(elevel, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied to set parameter \"%s\"", - name))); - return 0; + /* + * Check whether the current user has been granted privilege + * to set this GUC. + */ + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET); + if (aclresult != ACLCHECK_OK) + { + /* No granted privilege */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + return 0; + } } /* fall through to process the same as PGC_BACKEND */ /* FALLTHROUGH */ @@ -7568,11 +7638,22 @@ set_config_option(const char *name, const char *value, case PGC_SUSET: if (context == PGC_USERSET || context == PGC_BACKEND) { - ereport(elevel, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied to set parameter \"%s\"", - name))); - return 0; + /* + * Check whether the current user has been granted privilege + * to set this GUC. + */ + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET); + if (aclresult != ACLCHECK_OK) + { + /* No granted privilege */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + return 0; + } } break; case PGC_USERSET: @@ -8617,11 +8698,6 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) char AutoConfFileName[MAXPGPATH]; char AutoConfTmpFileName[MAXPGPATH]; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to execute ALTER SYSTEM command"))); - /* * Extract statement arguments */ @@ -8650,6 +8726,29 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) } /* + * Check permission to run ALTER SYSTEM on the target variable + */ + if (!superuser()) + { + if (resetall) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to perform ALTER SYSTEM RESET ALL"))); + else + { + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, GetUserId(), + ACL_ALTER_SYSTEM); + if (aclresult != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + } + } + + /* * Unless it's RESET_ALL, validate the target variable and value */ if (!resetall) @@ -8760,13 +8859,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) } /* - * Invoke the post-alter hook for altering this GUC variable. + * Invoke the post-alter hook for setting this GUC variable. GUCs + * typically do not have corresponding entries in pg_parameter_acl, so we + * call the hook using the name rather than a potentially-non-existent + * OID. Nonetheless, we pass ParameterAclRelationId so that this call + * context can be distinguished from others. (Note that "name" will be + * NULL in the RESET ALL case.) * * We do this here rather than at the end, because ALTER SYSTEM is not * transactional. If the hook aborts our transaction, it will be cleaner * to do so before we touch any files. */ - InvokeObjectPostAlterHookArgStr(InvalidOid, name, + InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, name, ACL_ALTER_SYSTEM, altersysstmt->setstmt->kind, false); @@ -8943,9 +9047,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) break; } - /* Invoke the post-alter hook for setting this GUC variable. */ - InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name, - ACL_SET_VALUE, stmt->kind, false); + /* Invoke the post-alter hook for setting this GUC variable, by name. */ + InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name, + ACL_SET, stmt->kind, false); } /* diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 6086d57cf3a..3e68dfc78f9 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -37,7 +37,7 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, * nspname: the namespace the object is in (NULL if none); not pre-quoted * type: the object type (as seen in GRANT command: must be one of * TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE, - * FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT) + * FOREIGN DATA WRAPPER, SERVER, PARAMETER or LARGE OBJECT) * acls: the ACL string fetched from the database * baseacls: the initial ACL string for this object * owner: username of object owner (will be passed through fmtId); can be @@ -501,6 +501,11 @@ do { \ CONVERT_PRIV('U', "USAGE"); else if (strcmp(type, "FOREIGN TABLE") == 0) CONVERT_PRIV('r', "SELECT"); + else if (strcmp(type, "PARAMETER") == 0) + { + CONVERT_PRIV('s', "SET"); + CONVERT_PRIV('A', "ALTER SYSTEM"); + } else if (strcmp(type, "LARGE OBJECT") == 0) { CONVERT_PRIV('r', "SELECT"); diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 9c9f7c6d63c..2dc33627630 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -18,6 +18,7 @@ #include <time.h> #include <unistd.h> +#include "catalog/pg_authid_d.h" #include "common/connect.h" #include "common/file_utils.h" #include "common/logging.h" @@ -36,6 +37,7 @@ static void help(void); static void dropRoles(PGconn *conn); static void dumpRoles(PGconn *conn); static void dumpRoleMembership(PGconn *conn); +static void dumpRoleGUCPrivs(PGconn *conn); static void dropTablespaces(PGconn *conn); static void dumpTablespaces(PGconn *conn); static void dropDBs(PGconn *conn); @@ -585,6 +587,10 @@ main(int argc, char *argv[]) /* Dump role memberships */ dumpRoleMembership(conn); + + /* Dump role GUC privileges */ + if (server_version >= 150000 && !skip_acls) + dumpRoleGUCPrivs(conn); } /* Dump tablespaces */ @@ -990,6 +996,65 @@ dumpRoleMembership(PGconn *conn) /* + * Dump role configuration parameter privileges. This code is used for 15.0 + * and later servers. + * + * Note: we expect dumpRoles already created all the roles, but there are + * no per-role configuration parameter privileges yet. + */ +static void +dumpRoleGUCPrivs(PGconn *conn) +{ + PGresult *res; + int i; + + /* + * Get all parameters that have non-default acls defined. + */ + res = executeQuery(conn, "SELECT parname, " + "pg_catalog.pg_get_userbyid(" CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS parowner, " + "paracl, " + "pg_catalog.acldefault('p', " CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS acldefault " + "FROM pg_catalog.pg_parameter_acl " + "ORDER BY 1"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Role privileges on configuration parameters\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + PQExpBuffer buf = createPQExpBuffer(); + char *parname = PQgetvalue(res, i, 0); + char *parowner = PQgetvalue(res, i, 1); + char *paracl = PQgetvalue(res, i, 2); + char *acldefault = PQgetvalue(res, i, 3); + char *fparname; + + /* needed for buildACLCommands() */ + fparname = pg_strdup(fmtId(parname)); + + if (!buildACLCommands(fparname, NULL, NULL, "PARAMETER", + paracl, acldefault, + parowner, "", server_version, buf)) + { + pg_log_error("could not parse ACL list (%s) for parameter \"%s\"", + paracl, parname); + PQfinish(conn); + exit_nicely(1); + } + + fprintf(OPF, "%s", buf->data); + + free(fparname); + destroyPQExpBuffer(buf); + } + + PQclear(res); + fprintf(OPF, "\n\n"); +} + + +/* * Drop tablespaces. */ static void diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 645d46ab75a..32d0b4855f5 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3737,7 +3737,8 @@ psql_completion(const char *text, int start, int end) * ALTER DEFAULT PRIVILEGES, so use TailMatches */ /* Complete GRANT/REVOKE with a list of roles and privileges */ - else if (TailMatches("GRANT|REVOKE")) + else if (TailMatches("GRANT|REVOKE") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR")) { /* * With ALTER DEFAULT PRIVILEGES, restrict completion to grantable @@ -3749,6 +3750,7 @@ psql_completion(const char *text, int start, int end) "EXECUTE", "USAGE", "ALL"); else COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + "GRANT", "SELECT", "INSERT", "UPDATE", @@ -3761,14 +3763,48 @@ psql_completion(const char *text, int start, int end) "TEMPORARY", "EXECUTE", "USAGE", + "SET", + "ALTER SYSTEM", "ALL"); } + else if (TailMatches("REVOKE", "GRANT")) + COMPLETE_WITH("OPTION FOR"); + else if (TailMatches("REVOKE", "GRANT", "OPTION")) + COMPLETE_WITH("FOR"); + + else if (TailMatches("GRANT|REVOKE", "ALTER") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "ALTER")) + COMPLETE_WITH("SYSTEM"); + + else if (TailMatches("GRANT|REVOKE", "SET") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "SET") || + TailMatches("GRANT|REVOKE", "ALTER", "SYSTEM") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "ALTER", "SYSTEM")) + COMPLETE_WITH("ON PARAMETER"); + + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "PARAMETER") || + TailMatches("GRANT|REVOKE", MatchAny, MatchAny, "ON", "PARAMETER") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "PARAMETER") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, MatchAny, "ON", "PARAMETER")) + COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars); + + else if (TailMatches("GRANT", MatchAny, "ON", "PARAMETER", MatchAny) || + TailMatches("GRANT", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny)) + COMPLETE_WITH("TO"); + + else if (TailMatches("REVOKE", MatchAny, "ON", "PARAMETER", MatchAny) || + TailMatches("REVOKE", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny) || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "PARAMETER", MatchAny) || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny)) + COMPLETE_WITH("FROM"); + /* * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with * TO/FROM */ - else if (TailMatches("GRANT|REVOKE", MatchAny)) + else if (TailMatches("GRANT|REVOKE", MatchAny) || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny)) { if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL")) COMPLETE_WITH("ON"); @@ -3785,7 +3821,8 @@ psql_completion(const char *text, int start, int end) * here will only work if the privilege list contains exactly one * privilege. */ - else if (TailMatches("GRANT|REVOKE", MatchAny, "ON")) + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON")) { /* * With ALTER DEFAULT PRIVILEGES, restrict completion to the kinds of @@ -3807,6 +3844,7 @@ psql_completion(const char *text, int start, int end) "FUNCTION", "LANGUAGE", "LARGE OBJECT", + "PARAMETER", "PROCEDURE", "ROUTINE", "SCHEMA", @@ -3815,13 +3853,15 @@ psql_completion(const char *text, int start, int end) "TABLESPACE", "TYPE"); } - else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL")) + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL")) COMPLETE_WITH("FUNCTIONS IN SCHEMA", "PROCEDURES IN SCHEMA", "ROUTINES IN SCHEMA", "SEQUENCES IN SCHEMA", "TABLES IN SCHEMA"); - else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN")) + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN") || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN")) COMPLETE_WITH("DATA WRAPPER", "SERVER"); /* @@ -3830,7 +3870,8 @@ psql_completion(const char *text, int start, int end) * * Complete "GRANT/REVOKE * ON *" with "TO/FROM". */ - else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny)) + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny) || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", MatchAny)) { if (TailMatches("DATABASE")) COMPLETE_WITH_QUERY(Query_for_list_of_databases); @@ -3868,6 +3909,22 @@ psql_completion(const char *text, int start, int end) (HeadMatches("REVOKE") && TailMatches("FROM"))) COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, Keywords_for_list_of_grant_roles); + + /* + * Offer grant options after that. + */ + else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny)) + COMPLETE_WITH("WITH ADMIN OPTION", + "WITH GRANT OPTION", + "GRANTED BY"); + else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH")) + COMPLETE_WITH("ADMIN OPTION", + "GRANT OPTION"); + else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH", MatchAny, "OPTION")) + COMPLETE_WITH("GRANTED BY"); + else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH", MatchAny, "OPTION", "GRANTED", "BY")) + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + Keywords_for_list_of_grant_roles); /* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */ else if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches("TO|FROM")) COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, @@ -3879,7 +3936,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("FROM"); /* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */ - else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny)) + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny) || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny)) { if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny)) COMPLETE_WITH("TO"); @@ -3888,7 +3946,8 @@ psql_completion(const char *text, int start, int end) } /* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */ - else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny)) + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny) || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny)) { if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny)) COMPLETE_WITH("TO"); @@ -3897,7 +3956,8 @@ psql_completion(const char *text, int start, int end) } /* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */ - else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny)) + else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny) || + TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny)) { if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny)) COMPLETE_WITH("TO"); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index cb26c967adc..ab28a56b3a0 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202203301 +#define CATALOG_VERSION_NO 202204061 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 344482ec877..d027075a4c3 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -120,6 +120,7 @@ typedef enum ObjectClass OCLASS_DEFACL, /* pg_default_acl */ OCLASS_EXTENSION, /* pg_extension */ OCLASS_EVENT_TRIGGER, /* pg_event_trigger */ + OCLASS_PARAMETER_ACL, /* pg_parameter_acl */ OCLASS_POLICY, /* pg_policy */ OCLASS_PUBLICATION, /* pg_publication */ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */ diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h index 4d54ae2a7d4..ac6adcb7300 100644 --- a/src/include/catalog/objectaccess.h +++ b/src/include/catalog/objectaccess.h @@ -239,7 +239,7 @@ extern void RunFunctionExecuteHookStr(const char *objectStr); RunObjectTruncateHookStr(objectName); \ } while(0) -#define InvokeObjectPostAlterHookStr(className,objectName,subId) \ +#define InvokeObjectPostAlterHookStr(classId,objectName,subId) \ InvokeObjectPostAlterHookArgStr((classId),(objectName),(subId), \ InvalidOid,false) #define InvokeObjectPostAlterHookArgStr(classId,objectName,subId, \ diff --git a/src/include/catalog/pg_parameter_acl.h b/src/include/catalog/pg_parameter_acl.h new file mode 100644 index 00000000000..8316391e51c --- /dev/null +++ b/src/include/catalog/pg_parameter_acl.h @@ -0,0 +1,62 @@ +/*------------------------------------------------------------------------- + * + * pg_parameter_acl.h + * definition of the "configuration parameter ACL" system catalog + * (pg_parameter_acl). + * + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_parameter_acl.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARAMETER_ACL_H +#define PG_PARAMETER_ACL_H + +#include "catalog/genbki.h" +#include "catalog/pg_parameter_acl_d.h" + +/* ---------------- + * pg_parameter_acl definition. cpp turns this into + * typedef struct FormData_pg_parameter_acl + * ---------------- + */ +CATALOG(pg_parameter_acl,8924,ParameterAclRelationId) BKI_SHARED_RELATION +{ + Oid oid; /* oid */ + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + /* name of parameter */ + text parname BKI_FORCE_NOT_NULL; + + /* access permissions */ + aclitem paracl[1] BKI_DEFAULT(_null_); +#endif +} FormData_pg_parameter_acl; + + +/* ---------------- + * Form_pg_parameter_acl corresponds to a pointer to a tuple with + * the format of pg_parameter_acl relation. + * ---------------- + */ +typedef FormData_pg_parameter_acl *Form_pg_parameter_acl; + +DECLARE_TOAST(pg_parameter_acl, 8925, 8926); +#define PgParameterAclToastTable 8925 +#define PgParameterAclToastIndex 8926 + +DECLARE_UNIQUE_INDEX(pg_parameter_acl_parname_index, 8927, ParameterAclParnameIndexId, on pg_parameter_acl using btree(parname text_ops)); +DECLARE_UNIQUE_INDEX_PKEY(pg_parameter_acl_oid_index, 8928, ParameterAclOidIndexId, on pg_parameter_acl using btree(oid oid_ops)); + + +extern Oid ParameterAclLookup(const char *parameter, bool missing_ok); +extern Oid ParameterAclCreate(const char *parameter); + +#endif /* PG_PARAMETER_ACL_H */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 25304430f44..4d285ece8bf 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7213,6 +7213,22 @@ proname => 'has_type_privilege', provolatile => 's', prorettype => 'bool', proargtypes => 'oid text', prosrc => 'has_type_privilege_id' }, +{ oid => '8050', + descr => 'user privilege on parameter by username, parameter name', + proname => 'has_parameter_privilege', provolatile => 's', + prorettype => 'bool', proargtypes => 'name text text', + prosrc => 'has_parameter_privilege_name_name' }, +{ oid => '8051', + descr => 'user privilege on parameter by user oid, parameter name', + proname => 'has_parameter_privilege', provolatile => 's', + prorettype => 'bool', proargtypes => 'oid text text', + prosrc => 'has_parameter_privilege_id_name' }, +{ oid => '8052', + descr => 'current user privilege on parameter by parameter name', + proname => 'has_parameter_privilege', provolatile => 's', + prorettype => 'bool', proargtypes => 'text text', + prosrc => 'has_parameter_privilege_name' }, + { oid => '2705', descr => 'user privilege on role by username, role name', proname => 'pg_has_role', provolatile => 's', prorettype => 'bool', proargtypes => 'name name text', prosrc => 'pg_has_role_name_name' }, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4a2ca81f3c0..8998d345605 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -92,8 +92,8 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ #define ACL_CREATE (1<<9) /* for namespaces and databases */ #define ACL_CREATE_TEMP (1<<10) /* for databases */ #define ACL_CONNECT (1<<11) /* for databases */ -#define ACL_SET_VALUE (1<<12) /* for configuration parameters */ -#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */ +#define ACL_SET (1<<12) /* for configuration parameters */ +#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */ #define N_ACL_RIGHTS 14 /* 1 plus the last 1<<x */ #define ACL_NO_RIGHTS 0 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */ @@ -2162,6 +2162,7 @@ typedef enum ObjectType OBJECT_OPCLASS, OBJECT_OPERATOR, OBJECT_OPFAMILY, + OBJECT_PARAMETER_ACL, OBJECT_POLICY, OBJECT_PROCEDURE, OBJECT_PUBLICATION, diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 0caa7310f2d..8a2ab405a28 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -329,6 +329,7 @@ PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 91ce3d8e9c3..48f7d72add5 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -146,7 +146,7 @@ typedef struct ArrayType Acl; #define ACL_CREATE_CHR 'C' #define ACL_CREATE_TEMP_CHR 'T' #define ACL_CONNECT_CHR 'c' -#define ACL_SET_VALUE_CHR 's' +#define ACL_SET_CHR 's' #define ACL_ALTER_SYSTEM_CHR 'A' /* string holding all privilege code chars, in order by bitmask position */ @@ -164,6 +164,7 @@ typedef struct ArrayType Acl; #define ACL_ALL_RIGHTS_FUNCTION (ACL_EXECUTE) #define ACL_ALL_RIGHTS_LANGUAGE (ACL_USAGE) #define ACL_ALL_RIGHTS_LARGEOBJECT (ACL_SELECT|ACL_UPDATE) +#define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM) #define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE) #define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE) #define ACL_ALL_RIGHTS_TYPE (ACL_USAGE) @@ -245,6 +246,10 @@ extern AclMode pg_class_aclmask_ext(Oid table_oid, Oid roleid, bool *is_missing); extern AclMode pg_database_aclmask(Oid db_oid, Oid roleid, AclMode mask, AclMaskHow how); +extern AclMode pg_parameter_aclmask(const char *name, Oid roleid, + AclMode mask, AclMaskHow how); +extern AclMode pg_parameter_acl_aclmask(Oid acl_oid, Oid roleid, + AclMode mask, AclMaskHow how); extern AclMode pg_proc_aclmask(Oid proc_oid, Oid roleid, AclMode mask, AclMaskHow how); extern AclMode pg_language_aclmask(Oid lang_oid, Oid roleid, @@ -273,6 +278,10 @@ extern AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode); extern AclResult pg_class_aclcheck_ext(Oid table_oid, Oid roleid, AclMode mode, bool *is_missing); extern AclResult pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode); +extern AclResult pg_parameter_aclcheck(const char *name, Oid roleid, + AclMode mode); +extern AclResult pg_parameter_acl_aclcheck(Oid acl_oid, Oid roleid, + AclMode mode); extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode); extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode); extern AclResult pg_largeobject_aclcheck_snapshot(Oid lang_oid, Oid roleid, diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ea774968f07..3446334e906 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -364,6 +364,8 @@ extern const char *GetConfigOption(const char *name, bool missing_ok, extern const char *GetConfigOptionResetString(const char *name); extern int GetConfigOptionFlags(const char *name, bool missing_ok); extern void ProcessConfigFile(GucContext context); +extern char *convert_GUC_name_for_parameter_acl(const char *name); +extern bool check_GUC_name_for_parameter_acl(const char *name); extern void InitializeGUCOptions(void); extern bool SelectConfigFiles(const char *userDoption, const char *progname); extern void ResetAllOptions(void); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 9c1a76e8bb6..4463ea66bea 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -72,6 +72,8 @@ enum SysCacheIdentifier OPEROID, OPFAMILYAMNAMENSP, OPFAMILYOID, + PARAMETERACLNAME, + PARAMETERACLOID, PARTRELID, PROCNAMEARGSNSP, PROCOID, diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out index 45ff276f7e9..39b274b8fa0 100644 --- a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out +++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out @@ -1,36 +1,84 @@ +-- Creating privileges on a placeholder GUC should create entries in the +-- pg_parameter_acl catalog which conservatively grant no privileges to public. +CREATE ROLE regress_role_joe; +GRANT SET ON PARAMETER test_oat_hooks.user_var1 TO regress_role_joe; +GRANT SET ON PARAMETER test_oat_hooks.super_var1 TO regress_role_joe; -- SET commands fire both the ProcessUtility_hook and the -- object_access_hook_str. Since the auditing GUC starts out false, we miss the -- initial "attempting" audit message from the ProcessUtility_hook, but we --- should thereafter see the audit messages +-- should thereafter see the audit messages. LOAD 'test_oat_hooks'; SET test_oat_hooks.audit = true; -NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit] -NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.audit] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.audit] NOTICE: in process utility: superuser finished set +-- Creating privileges on an existent custom GUC should create precisely the +-- right privileges, not overly conservative ones. +GRANT SET ON PARAMETER test_oat_hooks.user_var2 TO regress_role_joe; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +GRANT SET ON PARAMETER test_oat_hooks.super_var2 TO regress_role_joe; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +-- Granting multiple privileges on a parameter should be reported correctly to +-- the OAT hook, but beware that WITH GRANT OPTION is not represented. +GRANT SET, ALTER SYSTEM ON PARAMETER none.such TO regress_role_joe; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +GRANT SET, ALTER SYSTEM ON PARAMETER another.bogus TO regress_role_joe WITH GRANT OPTION; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +-- Check when the hooks fire relative to dependency based abort of a drop +DROP ROLE regress_role_joe; +NOTICE: in process utility: superuser attempting DropRoleStmt +NOTICE: in object access: superuser attempting drop (subId=0x0) [] +NOTICE: in object access: superuser finished drop (subId=0x0) [] +ERROR: role "regress_role_joe" cannot be dropped because some objects depend on it +DETAIL: privileges for parameter test_oat_hooks.user_var1 +privileges for parameter test_oat_hooks.super_var1 +privileges for parameter test_oat_hooks.user_var2 +privileges for parameter test_oat_hooks.super_var2 +privileges for parameter none.such +privileges for parameter another.bogus +-- Check the behavior of the hooks relative to do-nothing grants and revokes +GRANT SET ON PARAMETER work_mem TO PUBLIC; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +REVOKE ALTER SYSTEM ON PARAMETER work_mem FROM PUBLIC; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +-- Check the behavior of the hooks relative to unrecognized parameters +GRANT ALL ON PARAMETER "none.such" TO PUBLIC; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt +-- Check relative to an operation that causes the catalog entry to be deleted +REVOKE ALL ON PARAMETER "none.such" FROM PUBLIC; +NOTICE: in process utility: superuser attempting GrantStmt +NOTICE: in process utility: superuser finished GrantStmt -- Create objects for use in the test CREATE USER regress_test_user; NOTICE: in process utility: superuser attempting CreateRoleStmt -NOTICE: in object access: superuser attempting create (subId=0) [explicit] -NOTICE: in object access: superuser finished create (subId=0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0x0) [explicit] +NOTICE: in object access: superuser finished create (subId=0x0) [explicit] NOTICE: in process utility: superuser finished CreateRoleStmt CREATE TABLE regress_test_table (t text); NOTICE: in process utility: superuser attempting CreateStmt -NOTICE: in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed] LINE 1: CREATE TABLE regress_test_table (t text); ^ -NOTICE: in object access: superuser finished namespace search (subId=0) [no report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed] LINE 1: CREATE TABLE regress_test_table (t text); ^ -NOTICE: in object access: superuser attempting create (subId=0) [explicit] -NOTICE: in object access: superuser finished create (subId=0) [explicit] -NOTICE: in object access: superuser attempting create (subId=0) [explicit] -NOTICE: in object access: superuser finished create (subId=0) [explicit] -NOTICE: in object access: superuser attempting create (subId=0) [explicit] -NOTICE: in object access: superuser finished create (subId=0) [explicit] -NOTICE: in object access: superuser attempting create (subId=0) [internal] -NOTICE: in object access: superuser finished create (subId=0) [internal] -NOTICE: in object access: superuser attempting create (subId=0) [internal] -NOTICE: in object access: superuser finished create (subId=0) [internal] +NOTICE: in object access: superuser attempting create (subId=0x0) [explicit] +NOTICE: in object access: superuser finished create (subId=0x0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0x0) [explicit] +NOTICE: in object access: superuser finished create (subId=0x0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0x0) [explicit] +NOTICE: in object access: superuser finished create (subId=0x0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0x0) [internal] +NOTICE: in object access: superuser finished create (subId=0x0) [internal] +NOTICE: in object access: superuser attempting create (subId=0x0) [internal] +NOTICE: in object access: superuser finished create (subId=0x0) [internal] NOTICE: in process utility: superuser finished CreateStmt GRANT SELECT ON Table regress_test_table TO public; NOTICE: in process utility: superuser attempting GrantStmt @@ -39,8 +87,8 @@ CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$ SELECT $1; $$ LANGUAGE sql; NOTICE: in process utility: superuser attempting CreateFunctionStmt -NOTICE: in object access: superuser attempting create (subId=0) [explicit] -NOTICE: in object access: superuser finished create (subId=0) [explicit] +NOTICE: in object access: superuser attempting create (subId=0x0) [explicit] +NOTICE: in object access: superuser finished create (subId=0x0) [explicit] NOTICE: in process utility: superuser finished CreateFunctionStmt GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public; NOTICE: in process utility: superuser attempting GrantStmt @@ -63,35 +111,35 @@ NOTICE: in executor check perms: superuser finished execute SET work_mem = 8192; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem] NOTICE: in process utility: superuser finished set RESET work_mem; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem] NOTICE: in process utility: superuser finished set ALTER SYSTEM SET work_mem = 8192; NOTICE: in process utility: superuser attempting alter system -NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem] NOTICE: in process utility: superuser finished alter system ALTER SYSTEM RESET work_mem; NOTICE: in process utility: superuser attempting alter system -NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem] NOTICE: in process utility: superuser finished alter system -- Do those same things as non-superuser SET SESSION AUTHORIZATION regress_test_user; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [session_authorization] -NOTICE: in object_access_hook_str: non-superuser finished alter (set) [session_authorization] +NOTICE: in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [session_authorization] +NOTICE: in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [session_authorization] NOTICE: in process utility: non-superuser finished set SELECT * FROM regress_test_table; -NOTICE: in object access: non-superuser attempting namespace search (subId=0) [no report on violation, allowed] +NOTICE: in object access: non-superuser attempting namespace search (subId=0x0) [no report on violation, allowed] LINE 1: SELECT * FROM regress_test_table; ^ -NOTICE: in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed] +NOTICE: in object access: non-superuser finished namespace search (subId=0x0) [no report on violation, allowed] LINE 1: SELECT * FROM regress_test_table; ^ NOTICE: in executor check perms: non-superuser attempting execute @@ -110,61 +158,89 @@ NOTICE: in executor check perms: non-superuser finished execute SET work_mem = 8192; NOTICE: in process utility: non-superuser attempting set -NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [work_mem] -NOTICE: in object_access_hook_str: non-superuser finished alter (set) [work_mem] +NOTICE: in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [work_mem] +NOTICE: in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [work_mem] NOTICE: in process utility: non-superuser finished set RESET work_mem; NOTICE: in process utility: non-superuser attempting set -NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [work_mem] -NOTICE: in object_access_hook_str: non-superuser finished alter (set) [work_mem] +NOTICE: in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [work_mem] +NOTICE: in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [work_mem] NOTICE: in process utility: non-superuser finished set ALTER SYSTEM SET work_mem = 8192; NOTICE: in process utility: non-superuser attempting alter system -ERROR: must be superuser to execute ALTER SYSTEM command +ERROR: permission denied to set parameter "work_mem" ALTER SYSTEM RESET work_mem; NOTICE: in process utility: non-superuser attempting alter system -ERROR: must be superuser to execute ALTER SYSTEM command +ERROR: permission denied to set parameter "work_mem" +SET test_oat_hooks.user_var1 = true; +NOTICE: in process utility: non-superuser attempting set +NOTICE: in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [test_oat_hooks.user_var1] +NOTICE: in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [test_oat_hooks.user_var1] +NOTICE: in process utility: non-superuser finished set +SET test_oat_hooks.super_var1 = true; +NOTICE: in process utility: non-superuser attempting set +ERROR: permission denied to set parameter "test_oat_hooks.super_var1" +ALTER SYSTEM SET test_oat_hooks.user_var1 = true; +NOTICE: in process utility: non-superuser attempting alter system +ERROR: permission denied to set parameter "test_oat_hooks.user_var1" +ALTER SYSTEM SET test_oat_hooks.super_var1 = true; +NOTICE: in process utility: non-superuser attempting alter system +ERROR: permission denied to set parameter "test_oat_hooks.super_var1" +SET test_oat_hooks.user_var2 = true; +NOTICE: in process utility: non-superuser attempting set +NOTICE: in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [test_oat_hooks.user_var2] +NOTICE: in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [test_oat_hooks.user_var2] +NOTICE: in process utility: non-superuser finished set +SET test_oat_hooks.super_var2 = true; +NOTICE: in process utility: non-superuser attempting set +ERROR: permission denied to set parameter "test_oat_hooks.super_var2" +ALTER SYSTEM SET test_oat_hooks.user_var2 = true; +NOTICE: in process utility: non-superuser attempting alter system +ERROR: permission denied to set parameter "test_oat_hooks.user_var2" +ALTER SYSTEM SET test_oat_hooks.super_var2 = true; +NOTICE: in process utility: non-superuser attempting alter system +ERROR: permission denied to set parameter "test_oat_hooks.super_var2" RESET SESSION AUTHORIZATION; NOTICE: in process utility: non-superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [session_authorization] -NOTICE: in object_access_hook_str: superuser finished alter (set) [session_authorization] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [session_authorization] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [session_authorization] NOTICE: in process utility: superuser finished set -- Turn off non-superuser permissions SET test_oat_hooks.deny_set_variable = true; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_set_variable] -NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_set_variable] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_set_variable] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_set_variable] NOTICE: in process utility: superuser finished set SET test_oat_hooks.deny_alter_system = true; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_alter_system] -NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_alter_system] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_alter_system] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_alter_system] NOTICE: in process utility: superuser finished set SET test_oat_hooks.deny_object_access = true; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_object_access] -NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_object_access] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_object_access] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_object_access] NOTICE: in process utility: superuser finished set SET test_oat_hooks.deny_exec_perms = true; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_exec_perms] -NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_exec_perms] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_exec_perms] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_exec_perms] NOTICE: in process utility: superuser finished set SET test_oat_hooks.deny_utility_commands = true; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_utility_commands] -NOTICE: in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_utility_commands] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_utility_commands] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_utility_commands] NOTICE: in process utility: superuser finished set -- Try again as non-superuser with permissions denied SET SESSION AUTHORIZATION regress_test_user; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: non-superuser attempting alter (set) [session_authorization] +NOTICE: in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [session_authorization] ERROR: permission denied: set session_authorization SELECT * FROM regress_test_table; -NOTICE: in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed] +NOTICE: in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed] LINE 1: SELECT * FROM regress_test_table; ^ -NOTICE: in object access: superuser finished namespace search (subId=0) [no report on violation, allowed] +NOTICE: in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed] LINE 1: SELECT * FROM regress_test_table; ^ NOTICE: in executor check perms: superuser attempting execute @@ -183,28 +259,43 @@ NOTICE: in executor check perms: superuser finished execute SET work_mem = 8192; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem] NOTICE: in process utility: superuser finished set RESET work_mem; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem] NOTICE: in process utility: superuser finished set ALTER SYSTEM SET work_mem = 8192; NOTICE: in process utility: superuser attempting alter system -NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem] NOTICE: in process utility: superuser finished alter system ALTER SYSTEM RESET work_mem; NOTICE: in process utility: superuser attempting alter system -NOTICE: in object_access_hook_str: superuser attempting alter (alter system set) [work_mem] -NOTICE: in object_access_hook_str: superuser finished alter (alter system set) [work_mem] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem] NOTICE: in process utility: superuser finished alter system +-- Clean up RESET SESSION AUTHORIZATION; NOTICE: in process utility: superuser attempting set -NOTICE: in object_access_hook_str: superuser attempting alter (set) [session_authorization] -NOTICE: in object_access_hook_str: superuser finished alter (set) [session_authorization] +NOTICE: in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [session_authorization] +NOTICE: in object_access_hook_str: superuser finished alter (subId=0x1000, set) [session_authorization] NOTICE: in process utility: superuser finished set SET test_oat_hooks.audit = false; NOTICE: in process utility: superuser attempting set +DROP ROLE regress_role_joe; -- fails +ERROR: role "regress_role_joe" cannot be dropped because some objects depend on it +DETAIL: privileges for parameter test_oat_hooks.user_var1 +privileges for parameter test_oat_hooks.super_var1 +privileges for parameter test_oat_hooks.user_var2 +privileges for parameter test_oat_hooks.super_var2 +privileges for parameter none.such +privileges for parameter another.bogus +REVOKE ALL PRIVILEGES ON PARAMETER + none.such, another.bogus, + test_oat_hooks.user_var1, test_oat_hooks.super_var1, + test_oat_hooks.user_var2, test_oat_hooks.super_var2 + FROM regress_role_joe; +DROP ROLE regress_role_joe; diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql index 09e61864ee2..8b6d5373aa5 100644 --- a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql +++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql @@ -1,10 +1,39 @@ +-- Creating privileges on a placeholder GUC should create entries in the +-- pg_parameter_acl catalog which conservatively grant no privileges to public. +CREATE ROLE regress_role_joe; +GRANT SET ON PARAMETER test_oat_hooks.user_var1 TO regress_role_joe; +GRANT SET ON PARAMETER test_oat_hooks.super_var1 TO regress_role_joe; + -- SET commands fire both the ProcessUtility_hook and the -- object_access_hook_str. Since the auditing GUC starts out false, we miss the -- initial "attempting" audit message from the ProcessUtility_hook, but we --- should thereafter see the audit messages +-- should thereafter see the audit messages. LOAD 'test_oat_hooks'; SET test_oat_hooks.audit = true; +-- Creating privileges on an existent custom GUC should create precisely the +-- right privileges, not overly conservative ones. +GRANT SET ON PARAMETER test_oat_hooks.user_var2 TO regress_role_joe; +GRANT SET ON PARAMETER test_oat_hooks.super_var2 TO regress_role_joe; + +-- Granting multiple privileges on a parameter should be reported correctly to +-- the OAT hook, but beware that WITH GRANT OPTION is not represented. +GRANT SET, ALTER SYSTEM ON PARAMETER none.such TO regress_role_joe; +GRANT SET, ALTER SYSTEM ON PARAMETER another.bogus TO regress_role_joe WITH GRANT OPTION; + +-- Check when the hooks fire relative to dependency based abort of a drop +DROP ROLE regress_role_joe; + +-- Check the behavior of the hooks relative to do-nothing grants and revokes +GRANT SET ON PARAMETER work_mem TO PUBLIC; +REVOKE ALTER SYSTEM ON PARAMETER work_mem FROM PUBLIC; + +-- Check the behavior of the hooks relative to unrecognized parameters +GRANT ALL ON PARAMETER "none.such" TO PUBLIC; + +-- Check relative to an operation that causes the catalog entry to be deleted +REVOKE ALL ON PARAMETER "none.such" FROM PUBLIC; + -- Create objects for use in the test CREATE USER regress_test_user; CREATE TABLE regress_test_table (t text); @@ -30,6 +59,16 @@ SET work_mem = 8192; RESET work_mem; ALTER SYSTEM SET work_mem = 8192; ALTER SYSTEM RESET work_mem; + +SET test_oat_hooks.user_var1 = true; +SET test_oat_hooks.super_var1 = true; +ALTER SYSTEM SET test_oat_hooks.user_var1 = true; +ALTER SYSTEM SET test_oat_hooks.super_var1 = true; +SET test_oat_hooks.user_var2 = true; +SET test_oat_hooks.super_var2 = true; +ALTER SYSTEM SET test_oat_hooks.user_var2 = true; +ALTER SYSTEM SET test_oat_hooks.super_var2 = true; + RESET SESSION AUTHORIZATION; -- Turn off non-superuser permissions @@ -48,6 +87,14 @@ RESET work_mem; ALTER SYSTEM SET work_mem = 8192; ALTER SYSTEM RESET work_mem; +-- Clean up RESET SESSION AUTHORIZATION; SET test_oat_hooks.audit = false; +DROP ROLE regress_role_joe; -- fails +REVOKE ALL PRIVILEGES ON PARAMETER + none.such, another.bogus, + test_oat_hooks.user_var1, test_oat_hooks.super_var1, + test_oat_hooks.user_var2, test_oat_hooks.super_var2 + FROM regress_role_joe; +DROP ROLE regress_role_joe; diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c index eb7564ce22e..551da5d4986 100644 --- a/src/test/modules/test_oat_hooks/test_oat_hooks.c +++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c @@ -34,6 +34,15 @@ static bool REGRESS_deny_exec_perms = false; static bool REGRESS_deny_utility_commands = false; static bool REGRESS_audit = false; +/* + * GUCs for testing privileges on USERSET and SUSET variables, + * with and without privileges granted prior to module load. + */ +static bool REGRESS_userset_variable1 = false; +static bool REGRESS_userset_variable2 = false; +static bool REGRESS_suset_variable1 = false; +static bool REGRESS_suset_variable2 = false; + /* Saved hook values in case of unload */ static object_access_hook_type next_object_access_hook = NULL; static object_access_hook_type_str next_object_access_hook_str = NULL; @@ -153,6 +162,56 @@ _PG_init(void) NULL, NULL); + /* + * test_oat_hooks.user_var{1,2} = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.user_var1", + "Dummy parameter settable by public", + NULL, + ®RESS_userset_variable1, + false, + PGC_USERSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("test_oat_hooks.user_var2", + "Dummy parameter settable by public", + NULL, + ®RESS_userset_variable2, + false, + PGC_USERSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * test_oat_hooks.super_var{1,2} = (on|off) + */ + DefineCustomBoolVariable("test_oat_hooks.super_var1", + "Dummy parameter settable by superuser", + NULL, + ®RESS_suset_variable1, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("test_oat_hooks.super_var2", + "Dummy parameter settable by superuser", + NULL, + ®RESS_suset_variable2, + false, + PGC_SUSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + MarkGUCPrefixReserved("test_oat_hooks"); /* Object access hook */ @@ -250,7 +309,14 @@ REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char switch (access) { case OAT_POST_ALTER: - if (subId & ACL_SET_VALUE) + if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM)) + { + if (REGRESS_deny_set_variable && !superuser_arg(GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: all privileges %s", objName))); + } + else if (subId & ACL_SET) { if (REGRESS_deny_set_variable && !superuser_arg(GetUserId())) ereport(ERROR, @@ -265,7 +331,7 @@ REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char errmsg("permission denied: alter system set %s", objName))); } else - elog(ERROR, "Unknown SettingAclRelationId subId: %d", subId); + elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId); break; default: break; @@ -860,12 +926,14 @@ accesstype_to_string(ObjectAccessType access, int subId) type = "UNRECOGNIZED ObjectAccessType"; } - if (subId & ACL_SET_VALUE) - return psprintf("%s (set)", type); + if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM)) + return psprintf("%s (subId=0x%x, all privileges)", type, subId); + if (subId & ACL_SET) + return psprintf("%s (subId=0x%x, set)", type, subId); if (subId & ACL_ALTER_SYSTEM) - return psprintf("%s (alter system set)", type); + return psprintf("%s (subId=0x%x, alter system)", type, subId); - return psprintf("%s (subId=%d)", type, subId); + return psprintf("%s (subId=0x%x)", type, subId); } static char * diff --git a/src/test/modules/test_pg_dump/t/001_base.pl b/src/test/modules/test_pg_dump/t/001_base.pl index 84a35590b75..d842f934a3a 100644 --- a/src/test/modules/test_pg_dump/t/001_base.pl +++ b/src/test/modules/test_pg_dump/t/001_base.pl @@ -316,6 +316,38 @@ my %tests = ( like => { pg_dumpall_globals => 1, }, }, + 'GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role' + => { + create_order => 2, + create_sql => + 'GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role;', + regexp => + + qr/^GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role;/m, + like => { pg_dumpall_globals => 1, }, + }, + + 'GRANT ALL ON PARAMETER Custom.Knob TO regress_dump_test_role WITH GRANT OPTION' + => { + create_order => 2, + create_sql => + 'GRANT SET, ALTER SYSTEM ON PARAMETER Custom.Knob TO regress_dump_test_role WITH GRANT OPTION;', + regexp => + # "set" plus "alter system" is "all" privileges on parameters + qr/^GRANT ALL ON PARAMETER "custom.knob" TO regress_dump_test_role WITH GRANT OPTION;/m, + like => { pg_dumpall_globals => 1, }, + }, + + 'GRANT ALL ON PARAMETER DateStyle TO regress_dump_test_role' => { + create_order => 2, + create_sql => + 'GRANT ALL ON PARAMETER "DateStyle" TO regress_dump_test_role WITH GRANT OPTION; REVOKE GRANT OPTION FOR ALL ON PARAMETER DateStyle FROM regress_dump_test_role;', + regexp => + # The revoke simplifies the ultimate grant so as to not include "with grant option" + qr/^GRANT ALL ON PARAMETER datestyle TO regress_dump_test_role;/m, + like => { pg_dumpall_globals => 1, }, + }, + 'CREATE SCHEMA public' => { regexp => qr/^CREATE SCHEMA public;/m, like => { diff --git a/src/test/modules/unsafe_tests/Makefile b/src/test/modules/unsafe_tests/Makefile index 3ecf5fcfc5b..df582736884 100644 --- a/src/test/modules/unsafe_tests/Makefile +++ b/src/test/modules/unsafe_tests/Makefile @@ -1,6 +1,6 @@ # src/test/modules/unsafe_tests/Makefile -REGRESS = rolenames alter_system_table +REGRESS = rolenames alter_system_table guc_privs ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/src/test/modules/unsafe_tests/expected/guc_privs.out b/src/test/modules/unsafe_tests/expected/guc_privs.out new file mode 100644 index 00000000000..58dc87f958e --- /dev/null +++ b/src/test/modules/unsafe_tests/expected/guc_privs.out @@ -0,0 +1,526 @@ +-- +-- Tests for privileges on GUCs. +-- This is unsafe because changes will affect other databases in the cluster. +-- +-- Test with a superuser role. +CREATE ROLE regress_admin SUPERUSER; +-- Perform operations as user 'regress_admin'. +SET SESSION AUTHORIZATION regress_admin; +-- PGC_BACKEND +SET ignore_system_indexes = OFF; -- fail, cannot be set after connection start +ERROR: parameter "ignore_system_indexes" cannot be set after connection start +RESET ignore_system_indexes; -- fail, cannot be set after connection start +ERROR: parameter "ignore_system_indexes" cannot be set after connection start +ALTER SYSTEM SET ignore_system_indexes = OFF; -- ok +ALTER SYSTEM RESET ignore_system_indexes; -- ok +-- PGC_INTERNAL +SET block_size = 50; -- fail, cannot be changed +ERROR: parameter "block_size" cannot be changed +RESET block_size; -- fail, cannot be changed +ERROR: parameter "block_size" cannot be changed +ALTER SYSTEM SET block_size = 50; -- fail, cannot be changed +ERROR: parameter "block_size" cannot be changed +ALTER SYSTEM RESET block_size; -- fail, cannot be changed +ERROR: parameter "block_size" cannot be changed +-- PGC_POSTMASTER +SET autovacuum_freeze_max_age = 1000050000; -- fail, requires restart +ERROR: parameter "autovacuum_freeze_max_age" cannot be changed without restarting the server +RESET autovacuum_freeze_max_age; -- fail, requires restart +ERROR: parameter "autovacuum_freeze_max_age" cannot be changed without restarting the server +ALTER SYSTEM SET autovacuum_freeze_max_age = 1000050000; -- ok +ALTER SYSTEM RESET autovacuum_freeze_max_age; -- ok +ALTER SYSTEM SET config_file = '/usr/local/data/postgresql.conf'; -- fail, cannot be changed +ERROR: parameter "config_file" cannot be changed +ALTER SYSTEM RESET config_file; -- fail, cannot be changed +ERROR: parameter "config_file" cannot be changed +-- PGC_SIGHUP +SET autovacuum = OFF; -- fail, requires reload +ERROR: parameter "autovacuum" cannot be changed now +RESET autovacuum; -- fail, requires reload +ERROR: parameter "autovacuum" cannot be changed now +ALTER SYSTEM SET autovacuum = OFF; -- ok +ALTER SYSTEM RESET autovacuum; -- ok +-- PGC_SUSET +SET lc_messages = 'C'; -- ok +RESET lc_messages; -- ok +ALTER SYSTEM SET lc_messages = 'C'; -- ok +ALTER SYSTEM RESET lc_messages; -- ok +-- PGC_SU_BACKEND +SET jit_debugging_support = OFF; -- fail, cannot be set after connection start +ERROR: parameter "jit_debugging_support" cannot be set after connection start +RESET jit_debugging_support; -- fail, cannot be set after connection start +ERROR: parameter "jit_debugging_support" cannot be set after connection start +ALTER SYSTEM SET jit_debugging_support = OFF; -- ok +ALTER SYSTEM RESET jit_debugging_support; -- ok +-- PGC_USERSET +SET DateStyle = 'ISO, MDY'; -- ok +RESET DateStyle; -- ok +ALTER SYSTEM SET DateStyle = 'ISO, MDY'; -- ok +ALTER SYSTEM RESET DateStyle; -- ok +ALTER SYSTEM SET ssl_renegotiation_limit = 0; -- fail, cannot be changed +ERROR: parameter "ssl_renegotiation_limit" cannot be changed +ALTER SYSTEM RESET ssl_renegotiation_limit; -- fail, cannot be changed +ERROR: parameter "ssl_renegotiation_limit" cannot be changed +-- Finished testing superuser +-- Create non-superuser with privileges to configure host resource usage +CREATE ROLE regress_host_resource_admin NOSUPERUSER; +-- Revoke privileges not yet granted +REVOKE SET, ALTER SYSTEM ON PARAMETER work_mem FROM regress_host_resource_admin; +REVOKE SET, ALTER SYSTEM ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin; +-- Check the new role does not yet have privileges on parameters +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +-- Check inappropriate and nonsense privilege types +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE'); +ERROR: unrecognized privilege type: "SELECT" +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE'); +ERROR: unrecognized privilege type: "USAGE" +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER'); +ERROR: unrecognized privilege type: "WHATEVER" +-- Revoke, grant, and revoke again a SUSET parameter not yet granted +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +GRANT SET ON PARAMETER zero_damaged_pages TO regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +-- Revoke, grant, and revoke again a USERSET parameter not yet granted +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +GRANT SET ON PARAMETER work_mem TO regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +-- Revoke privileges from a non-existent custom GUC. This should not create +-- entries in the catalog. +REVOKE ALL ON PARAMETER "none.such" FROM regress_host_resource_admin; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such'; + ?column? +---------- +(0 rows) + +-- Grant and then revoke privileges on the non-existent custom GUC. Check that +-- a do-nothing entry is not left in the catalogs after the revoke. +GRANT ALL ON PARAMETER none.such TO regress_host_resource_admin; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such'; + ?column? +---------- + 1 +(1 row) + +REVOKE ALL ON PARAMETER "None.Such" FROM regress_host_resource_admin; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such'; + ?column? +---------- +(0 rows) + +-- Can't grant on a non-existent core GUC. +GRANT ALL ON PARAMETER no_such_guc TO regress_host_resource_admin; -- fail +ERROR: invalid parameter name "no_such_guc" +-- Initially there are no privileges and no catalog entry for this GUC. +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; + ?column? +---------- +(0 rows) + +-- GRANT SET creates an entry: +GRANT SET ON PARAMETER enable_material TO PUBLIC; +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; + ?column? +---------- + 1 +(1 row) + +-- Now grant ALTER SYSTEM: +GRANT ALL ON PARAMETER enable_material TO PUBLIC; +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; + ?column? +---------- + 1 +(1 row) + +-- REVOKE ALTER SYSTEM brings us back to just the SET privilege: +REVOKE ALTER SYSTEM ON PARAMETER enable_material FROM PUBLIC; +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + f +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; + ?column? +---------- + 1 +(1 row) + +-- And this should remove the entry altogether: +REVOKE SET ON PARAMETER enable_material FROM PUBLIC; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; + ?column? +---------- +(0 rows) + +-- Grant privileges on parameters to the new non-superuser role +GRANT SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +TO regress_host_resource_admin; +-- Check the new role now has privilges on parameters +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET WITH GRANT OPTION, ALTER SYSTEM WITH GRANT OPTION'); + has_parameter_privilege +------------------------- + f +(1 row) + +-- Check again the inappropriate and nonsense privilege types. The prior +-- similar check was performed before any entry for work_mem existed. +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE'); +ERROR: unrecognized privilege type: "SELECT" +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE'); +ERROR: unrecognized privilege type: "USAGE" +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER'); +ERROR: unrecognized privilege type: "WHATEVER" +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION'); +ERROR: unrecognized privilege type: "WHATEVER WITH GRANT OPTION" +-- Check other function signatures +SELECT has_parameter_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'), + 'max_stack_depth', + 'SET'); + has_parameter_privilege +------------------------- + t +(1 row) + +SELECT has_parameter_privilege('hash_mem_multiplier', 'set'); + has_parameter_privilege +------------------------- + t +(1 row) + +-- Check object identity functions +SELECT pg_describe_object(tableoid, oid, 0) +FROM pg_parameter_acl WHERE parname = 'work_mem'; + pg_describe_object +-------------------- + parameter work_mem +(1 row) + +SELECT pg_identify_object(tableoid, oid, 0) +FROM pg_parameter_acl WHERE parname = 'work_mem'; + pg_identify_object +------------------------------ + ("parameter ACL",,,work_mem) +(1 row) + +SELECT pg_identify_object_as_address(tableoid, oid, 0) +FROM pg_parameter_acl WHERE parname = 'work_mem'; + pg_identify_object_as_address +--------------------------------- + ("parameter ACL",{work_mem},{}) +(1 row) + +SELECT classid::regclass, + (SELECT parname FROM pg_parameter_acl WHERE oid = goa.objid) AS parname, + objsubid +FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa; + classid | parname | objsubid +------------------+----------+---------- + pg_parameter_acl | work_mem | 0 +(1 row) + +-- Perform some operations as user 'regress_host_resource_admin' +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- ok, privileges have been granted +ALTER SYSTEM SET ignore_system_indexes = OFF; -- fail, insufficient privileges +ERROR: permission denied to set parameter "ignore_system_indexes" +ALTER SYSTEM RESET autovacuum_multixact_freeze_max_age; -- fail, insufficient privileges +ERROR: permission denied to set parameter "autovacuum_multixact_freeze_max_age" +SET jit_provider = 'llvmjit'; -- fail, insufficient privileges +ERROR: parameter "jit_provider" cannot be changed without restarting the server +SELECT set_config ('jit_provider', 'llvmjit', true); -- fail, insufficient privileges +ERROR: parameter "jit_provider" cannot be changed without restarting the server +ALTER SYSTEM SET shared_buffers = 50; -- ok +ALTER SYSTEM RESET shared_buffers; -- ok +SET autovacuum_work_mem = 50; -- cannot be changed now +ERROR: parameter "autovacuum_work_mem" cannot be changed now +ALTER SYSTEM RESET temp_file_limit; -- ok +SET TimeZone = 'Europe/Helsinki'; -- ok +RESET TimeZone; -- ok +SET max_stack_depth = 2048; -- ok, privileges have been granted +RESET max_stack_depth; -- ok, privileges have been granted +ALTER SYSTEM SET max_stack_depth = 2048; -- ok, privileges have been granted +ALTER SYSTEM RESET max_stack_depth; -- ok, privileges have been granted +SET lc_messages = 'C'; -- fail, insufficient privileges +ERROR: permission denied to set parameter "lc_messages" +RESET lc_messages; -- fail, insufficient privileges +ERROR: permission denied to set parameter "lc_messages" +ALTER SYSTEM SET lc_messages = 'C'; -- fail, insufficient privileges +ERROR: permission denied to set parameter "lc_messages" +ALTER SYSTEM RESET lc_messages; -- fail, insufficient privileges +ERROR: permission denied to set parameter "lc_messages" +SELECT set_config ('temp_buffers', '8192', false); -- ok + set_config +------------ + 64MB +(1 row) + +ALTER SYSTEM RESET autovacuum_work_mem; -- ok, privileges have been granted +ALTER SYSTEM RESET ALL; -- fail, insufficient privileges +ERROR: permission denied to perform ALTER SYSTEM RESET ALL +-- Check dropping/revoking behavior +SET SESSION AUTHORIZATION regress_admin; +DROP ROLE regress_host_resource_admin; -- fail, privileges remain +ERROR: role "regress_host_resource_admin" cannot be dropped because some objects depend on it +DETAIL: privileges for parameter autovacuum_work_mem +privileges for parameter hash_mem_multiplier +privileges for parameter max_stack_depth +privileges for parameter shared_buffers +privileges for parameter temp_file_limit +privileges for parameter work_mem +-- Use "revoke" to remove the privileges and allow the role to be dropped +REVOKE SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +FROM regress_host_resource_admin; +DROP ROLE regress_host_resource_admin; -- ok +-- Try that again, but use "drop owned by" instead of "revoke" +CREATE ROLE regress_host_resource_admin NOSUPERUSER; +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- fail, privileges not yet granted +ERROR: permission denied to set parameter "autovacuum_work_mem" +SET SESSION AUTHORIZATION regress_admin; +GRANT SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +TO regress_host_resource_admin; +DROP ROLE regress_host_resource_admin; -- fail, privileges remain +ERROR: role "regress_host_resource_admin" cannot be dropped because some objects depend on it +DETAIL: privileges for parameter autovacuum_work_mem +privileges for parameter hash_mem_multiplier +privileges for parameter max_stack_depth +privileges for parameter shared_buffers +privileges for parameter temp_file_limit +privileges for parameter work_mem +DROP OWNED BY regress_host_resource_admin RESTRICT; -- cascade should not be needed +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- fail, "drop owned" has dropped privileges +ERROR: permission denied to set parameter "autovacuum_work_mem" +SET SESSION AUTHORIZATION regress_admin; +DROP ROLE regress_host_resource_admin; -- ok +-- Check that "reassign owned" doesn't affect privileges +CREATE ROLE regress_host_resource_admin NOSUPERUSER; +CREATE ROLE regress_host_resource_newadmin NOSUPERUSER; +GRANT SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +TO regress_host_resource_admin; +REASSIGN OWNED BY regress_host_resource_admin TO regress_host_resource_newadmin; +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- ok, "reassign owned" did not change privileges +ALTER SYSTEM RESET autovacuum_work_mem; -- ok +SET SESSION AUTHORIZATION regress_admin; +DROP ROLE regress_host_resource_admin; -- fail, privileges remain +ERROR: role "regress_host_resource_admin" cannot be dropped because some objects depend on it +DETAIL: privileges for parameter autovacuum_work_mem +privileges for parameter hash_mem_multiplier +privileges for parameter max_stack_depth +privileges for parameter shared_buffers +privileges for parameter temp_file_limit +privileges for parameter work_mem +DROP ROLE regress_host_resource_newadmin; -- ok, nothing was transferred +-- Use "drop owned by" so we can drop the role +DROP OWNED BY regress_host_resource_admin; -- ok +DROP ROLE regress_host_resource_admin; -- ok +-- Clean up +RESET SESSION AUTHORIZATION; +DROP ROLE regress_admin; -- ok diff --git a/src/test/modules/unsafe_tests/sql/guc_privs.sql b/src/test/modules/unsafe_tests/sql/guc_privs.sql new file mode 100644 index 00000000000..12b22548f06 --- /dev/null +++ b/src/test/modules/unsafe_tests/sql/guc_privs.sql @@ -0,0 +1,237 @@ +-- +-- Tests for privileges on GUCs. +-- This is unsafe because changes will affect other databases in the cluster. +-- + +-- Test with a superuser role. +CREATE ROLE regress_admin SUPERUSER; + +-- Perform operations as user 'regress_admin'. +SET SESSION AUTHORIZATION regress_admin; + +-- PGC_BACKEND +SET ignore_system_indexes = OFF; -- fail, cannot be set after connection start +RESET ignore_system_indexes; -- fail, cannot be set after connection start +ALTER SYSTEM SET ignore_system_indexes = OFF; -- ok +ALTER SYSTEM RESET ignore_system_indexes; -- ok +-- PGC_INTERNAL +SET block_size = 50; -- fail, cannot be changed +RESET block_size; -- fail, cannot be changed +ALTER SYSTEM SET block_size = 50; -- fail, cannot be changed +ALTER SYSTEM RESET block_size; -- fail, cannot be changed +-- PGC_POSTMASTER +SET autovacuum_freeze_max_age = 1000050000; -- fail, requires restart +RESET autovacuum_freeze_max_age; -- fail, requires restart +ALTER SYSTEM SET autovacuum_freeze_max_age = 1000050000; -- ok +ALTER SYSTEM RESET autovacuum_freeze_max_age; -- ok +ALTER SYSTEM SET config_file = '/usr/local/data/postgresql.conf'; -- fail, cannot be changed +ALTER SYSTEM RESET config_file; -- fail, cannot be changed +-- PGC_SIGHUP +SET autovacuum = OFF; -- fail, requires reload +RESET autovacuum; -- fail, requires reload +ALTER SYSTEM SET autovacuum = OFF; -- ok +ALTER SYSTEM RESET autovacuum; -- ok +-- PGC_SUSET +SET lc_messages = 'C'; -- ok +RESET lc_messages; -- ok +ALTER SYSTEM SET lc_messages = 'C'; -- ok +ALTER SYSTEM RESET lc_messages; -- ok +-- PGC_SU_BACKEND +SET jit_debugging_support = OFF; -- fail, cannot be set after connection start +RESET jit_debugging_support; -- fail, cannot be set after connection start +ALTER SYSTEM SET jit_debugging_support = OFF; -- ok +ALTER SYSTEM RESET jit_debugging_support; -- ok +-- PGC_USERSET +SET DateStyle = 'ISO, MDY'; -- ok +RESET DateStyle; -- ok +ALTER SYSTEM SET DateStyle = 'ISO, MDY'; -- ok +ALTER SYSTEM RESET DateStyle; -- ok +ALTER SYSTEM SET ssl_renegotiation_limit = 0; -- fail, cannot be changed +ALTER SYSTEM RESET ssl_renegotiation_limit; -- fail, cannot be changed +-- Finished testing superuser + +-- Create non-superuser with privileges to configure host resource usage +CREATE ROLE regress_host_resource_admin NOSUPERUSER; +-- Revoke privileges not yet granted +REVOKE SET, ALTER SYSTEM ON PARAMETER work_mem FROM regress_host_resource_admin; +REVOKE SET, ALTER SYSTEM ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin; +-- Check the new role does not yet have privileges on parameters +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); +-- Check inappropriate and nonsense privilege types +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER'); +-- Revoke, grant, and revoke again a SUSET parameter not yet granted +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); +REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); +GRANT SET ON PARAMETER zero_damaged_pages TO regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); +REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM'); +-- Revoke, grant, and revoke again a USERSET parameter not yet granted +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); +REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); +GRANT SET ON PARAMETER work_mem TO regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); +REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin; +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); + +-- Revoke privileges from a non-existent custom GUC. This should not create +-- entries in the catalog. +REVOKE ALL ON PARAMETER "none.such" FROM regress_host_resource_admin; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such'; +-- Grant and then revoke privileges on the non-existent custom GUC. Check that +-- a do-nothing entry is not left in the catalogs after the revoke. +GRANT ALL ON PARAMETER none.such TO regress_host_resource_admin; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such'; +REVOKE ALL ON PARAMETER "None.Such" FROM regress_host_resource_admin; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such'; +-- Can't grant on a non-existent core GUC. +GRANT ALL ON PARAMETER no_such_guc TO regress_host_resource_admin; -- fail + +-- Initially there are no privileges and no catalog entry for this GUC. +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; +-- GRANT SET creates an entry: +GRANT SET ON PARAMETER enable_material TO PUBLIC; +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; +-- Now grant ALTER SYSTEM: +GRANT ALL ON PARAMETER enable_material TO PUBLIC; +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; +-- REVOKE ALTER SYSTEM brings us back to just the SET privilege: +REVOKE ALTER SYSTEM ON PARAMETER enable_material FROM PUBLIC; +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM'); +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; +-- And this should remove the entry altogether: +REVOKE SET ON PARAMETER enable_material FROM PUBLIC; +SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material'; + +-- Grant privileges on parameters to the new non-superuser role +GRANT SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +TO regress_host_resource_admin; +-- Check the new role now has privilges on parameters +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET WITH GRANT OPTION, ALTER SYSTEM WITH GRANT OPTION'); +-- Check again the inappropriate and nonsense privilege types. The prior +-- similar check was performed before any entry for work_mem existed. +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER'); +SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION'); + +-- Check other function signatures +SELECT has_parameter_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'), + 'max_stack_depth', + 'SET'); +SELECT has_parameter_privilege('hash_mem_multiplier', 'set'); + +-- Check object identity functions +SELECT pg_describe_object(tableoid, oid, 0) +FROM pg_parameter_acl WHERE parname = 'work_mem'; +SELECT pg_identify_object(tableoid, oid, 0) +FROM pg_parameter_acl WHERE parname = 'work_mem'; +SELECT pg_identify_object_as_address(tableoid, oid, 0) +FROM pg_parameter_acl WHERE parname = 'work_mem'; +SELECT classid::regclass, + (SELECT parname FROM pg_parameter_acl WHERE oid = goa.objid) AS parname, + objsubid +FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa; + +-- Perform some operations as user 'regress_host_resource_admin' +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- ok, privileges have been granted +ALTER SYSTEM SET ignore_system_indexes = OFF; -- fail, insufficient privileges +ALTER SYSTEM RESET autovacuum_multixact_freeze_max_age; -- fail, insufficient privileges +SET jit_provider = 'llvmjit'; -- fail, insufficient privileges +SELECT set_config ('jit_provider', 'llvmjit', true); -- fail, insufficient privileges +ALTER SYSTEM SET shared_buffers = 50; -- ok +ALTER SYSTEM RESET shared_buffers; -- ok +SET autovacuum_work_mem = 50; -- cannot be changed now +ALTER SYSTEM RESET temp_file_limit; -- ok +SET TimeZone = 'Europe/Helsinki'; -- ok +RESET TimeZone; -- ok +SET max_stack_depth = 2048; -- ok, privileges have been granted +RESET max_stack_depth; -- ok, privileges have been granted +ALTER SYSTEM SET max_stack_depth = 2048; -- ok, privileges have been granted +ALTER SYSTEM RESET max_stack_depth; -- ok, privileges have been granted +SET lc_messages = 'C'; -- fail, insufficient privileges +RESET lc_messages; -- fail, insufficient privileges +ALTER SYSTEM SET lc_messages = 'C'; -- fail, insufficient privileges +ALTER SYSTEM RESET lc_messages; -- fail, insufficient privileges +SELECT set_config ('temp_buffers', '8192', false); -- ok +ALTER SYSTEM RESET autovacuum_work_mem; -- ok, privileges have been granted +ALTER SYSTEM RESET ALL; -- fail, insufficient privileges + +-- Check dropping/revoking behavior +SET SESSION AUTHORIZATION regress_admin; +DROP ROLE regress_host_resource_admin; -- fail, privileges remain +-- Use "revoke" to remove the privileges and allow the role to be dropped +REVOKE SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +FROM regress_host_resource_admin; +DROP ROLE regress_host_resource_admin; -- ok + +-- Try that again, but use "drop owned by" instead of "revoke" +CREATE ROLE regress_host_resource_admin NOSUPERUSER; +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- fail, privileges not yet granted +SET SESSION AUTHORIZATION regress_admin; +GRANT SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +TO regress_host_resource_admin; +DROP ROLE regress_host_resource_admin; -- fail, privileges remain +DROP OWNED BY regress_host_resource_admin RESTRICT; -- cascade should not be needed +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- fail, "drop owned" has dropped privileges +SET SESSION AUTHORIZATION regress_admin; +DROP ROLE regress_host_resource_admin; -- ok + +-- Check that "reassign owned" doesn't affect privileges +CREATE ROLE regress_host_resource_admin NOSUPERUSER; +CREATE ROLE regress_host_resource_newadmin NOSUPERUSER; +GRANT SET, ALTER SYSTEM ON PARAMETER + autovacuum_work_mem, hash_mem_multiplier, max_stack_depth, + shared_buffers, temp_file_limit, work_mem +TO regress_host_resource_admin; +REASSIGN OWNED BY regress_host_resource_admin TO regress_host_resource_newadmin; +SET SESSION AUTHORIZATION regress_host_resource_admin; +ALTER SYSTEM SET autovacuum_work_mem = 32; -- ok, "reassign owned" did not change privileges +ALTER SYSTEM RESET autovacuum_work_mem; -- ok +SET SESSION AUTHORIZATION regress_admin; +DROP ROLE regress_host_resource_admin; -- fail, privileges remain +DROP ROLE regress_host_resource_newadmin; -- ok, nothing was transferred +-- Use "drop owned by" so we can drop the role +DROP OWNED BY regress_host_resource_admin; -- ok +DROP ROLE regress_host_resource_admin; -- ok + +-- Clean up +RESET SESSION AUTHORIZATION; +DROP ROLE regress_admin; -- ok |