diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/Makefile | 4 | ||||
-rw-r--r-- | src/backend/catalog/aclchk.c | 357 | ||||
-rw-r--r-- | src/backend/catalog/catalog.c | 36 | ||||
-rw-r--r-- | src/backend/catalog/dependency.c | 6 | ||||
-rw-r--r-- | src/backend/catalog/objectaddress.c | 71 | ||||
-rw-r--r-- | src/backend/catalog/pg_parameter_acl.c | 118 | ||||
-rw-r--r-- | src/backend/commands/alter.c | 1 | ||||
-rw-r--r-- | src/backend/commands/event_trigger.c | 5 | ||||
-rw-r--r-- | src/backend/commands/seclabel.c | 1 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 1 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 46 | ||||
-rw-r--r-- | src/backend/utils/adt/acl.c | 114 | ||||
-rw-r--r-- | src/backend/utils/cache/syscache.c | 23 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 146 |
14 files changed, 886 insertions, 43 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); } /* |