Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/catalog/aclchk.c130
-rw-r--r--src/backend/catalog/pg_shdepend.c249
-rw-r--r--src/backend/utils/adt/acl.c6
3 files changed, 283 insertions, 102 deletions
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 143876b77ff..fe90102e6eb 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -4771,19 +4771,16 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid,
/* Update pg_shdepend for roles mentioned in the old/new ACLs. */
oldAclDatum = heap_getattr(oldtuple, Anum_pg_init_privs_initprivs,
RelationGetDescr(relation), &isNull);
- if (!isNull)
- old_acl = DatumGetAclP(oldAclDatum);
- else
- old_acl = NULL; /* this case shouldn't happen, probably */
+ Assert(!isNull);
+ old_acl = DatumGetAclP(oldAclDatum);
noldmembers = aclmembers(old_acl, &oldmembers);
updateInitAclDependencies(classoid, objoid, objsubid,
- ownerId,
noldmembers, oldmembers,
nnewmembers, newmembers);
/* If we have a new ACL to set, then update the row with it. */
- if (new_acl)
+ if (new_acl && ACL_NUM(new_acl) != 0)
{
values[Anum_pg_init_privs_initprivs - 1] = PointerGetDatum(new_acl);
replace[Anum_pg_init_privs_initprivs - 1] = true;
@@ -4795,7 +4792,7 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid,
}
else
{
- /* new_acl is NULL, so delete the entry we found. */
+ /* new_acl is NULL/empty, so delete the entry we found. */
CatalogTupleDelete(relation, &oldtuple->t_self);
}
}
@@ -4810,7 +4807,7 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid,
* If we are passed in a NULL ACL and no entry exists, we can just
* fall through and do nothing.
*/
- if (new_acl)
+ if (new_acl && ACL_NUM(new_acl) != 0)
{
/* No entry found, so add it. */
values[Anum_pg_init_privs_objoid - 1] = ObjectIdGetDatum(objoid);
@@ -4832,7 +4829,6 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid,
oldmembers = NULL;
updateInitAclDependencies(classoid, objoid, objsubid,
- ownerId,
noldmembers, oldmembers,
nnewmembers, newmembers);
}
@@ -4847,6 +4843,115 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid,
}
/*
+ * ReplaceRoleInInitPriv
+ *
+ * Used by shdepReassignOwned to replace mentions of a role in pg_init_privs.
+ */
+void
+ReplaceRoleInInitPriv(Oid oldroleid, Oid newroleid,
+ Oid classid, Oid objid, int32 objsubid)
+{
+ Relation rel;
+ ScanKeyData key[3];
+ SysScanDesc scan;
+ HeapTuple oldtuple;
+ Datum oldAclDatum;
+ bool isNull;
+ Acl *old_acl;
+ Acl *new_acl;
+ HeapTuple newtuple;
+ int noldmembers;
+ int nnewmembers;
+ Oid *oldmembers;
+ Oid *newmembers;
+
+ /* Search for existing pg_init_privs entry for the target object. */
+ rel = table_open(InitPrivsRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_init_privs_objoid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objid));
+ ScanKeyInit(&key[1],
+ Anum_pg_init_privs_classoid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(classid));
+ ScanKeyInit(&key[2],
+ Anum_pg_init_privs_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(objsubid));
+
+ scan = systable_beginscan(rel, InitPrivsObjIndexId, true,
+ NULL, 3, key);
+
+ /* There should exist only one entry or none. */
+ oldtuple = systable_getnext(scan);
+
+ if (!HeapTupleIsValid(oldtuple))
+ {
+ /*
+ * Hmm, why are we here if there's no entry? But pack up and go away
+ * quietly.
+ */
+ systable_endscan(scan);
+ table_close(rel, RowExclusiveLock);
+ return;
+ }
+
+ /* Get a writable copy of the existing ACL. */
+ oldAclDatum = heap_getattr(oldtuple, Anum_pg_init_privs_initprivs,
+ RelationGetDescr(rel), &isNull);
+ Assert(!isNull);
+ old_acl = DatumGetAclPCopy(oldAclDatum);
+
+ /*
+ * Generate new ACL. This usage of aclnewowner is a bit off-label when
+ * oldroleid isn't the owner; but it does the job fine.
+ */
+ new_acl = aclnewowner(old_acl, oldroleid, newroleid);
+
+ /*
+ * If we end with an empty ACL, delete the pg_init_privs entry. (That
+ * probably can't happen here, but we may as well cover the case.)
+ */
+ if (new_acl == NULL || ACL_NUM(new_acl) == 0)
+ {
+ CatalogTupleDelete(rel, &oldtuple->t_self);
+ }
+ else
+ {
+ Datum values[Natts_pg_init_privs] = {0};
+ bool nulls[Natts_pg_init_privs] = {0};
+ bool replaces[Natts_pg_init_privs] = {0};
+
+ /* Update existing entry. */
+ values[Anum_pg_init_privs_initprivs - 1] = PointerGetDatum(new_acl);
+ replaces[Anum_pg_init_privs_initprivs - 1] = true;
+
+ newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(rel),
+ values, nulls, replaces);
+ CatalogTupleUpdate(rel, &newtuple->t_self, newtuple);
+ }
+
+ /*
+ * Update the shared dependency ACL info.
+ */
+ noldmembers = aclmembers(old_acl, &oldmembers);
+ nnewmembers = aclmembers(new_acl, &newmembers);
+
+ updateInitAclDependencies(classid, objid, objsubid,
+ noldmembers, oldmembers,
+ nnewmembers, newmembers);
+
+ systable_endscan(scan);
+
+ /* prevent error when processing objects multiple times */
+ CommandCounterIncrement();
+
+ table_close(rel, RowExclusiveLock);
+}
+
+/*
* RemoveRoleFromInitPriv
*
* Used by shdepDropOwned to remove mentions of a role in pg_init_privs.
@@ -4907,10 +5012,8 @@ RemoveRoleFromInitPriv(Oid roleid, Oid classid, Oid objid, int32 objsubid)
/* Get a writable copy of the existing ACL. */
oldAclDatum = heap_getattr(oldtuple, Anum_pg_init_privs_initprivs,
RelationGetDescr(rel), &isNull);
- if (!isNull)
- old_acl = DatumGetAclPCopy(oldAclDatum);
- else
- old_acl = NULL; /* this case shouldn't happen, probably */
+ Assert(!isNull);
+ old_acl = DatumGetAclPCopy(oldAclDatum);
/*
* We need the members of both old and new ACLs so we can correct the
@@ -4972,7 +5075,6 @@ RemoveRoleFromInitPriv(Oid roleid, Oid classid, Oid objid, int32 objsubid)
nnewmembers = aclmembers(new_acl, &newmembers);
updateInitAclDependencies(classid, objid, objsubid,
- ownerId,
noldmembers, oldmembers,
nnewmembers, newmembers);
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 20bcfd779b9..753afb88453 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -103,6 +103,9 @@ static void storeObjectDescription(StringInfo descs,
ObjectAddress *object,
SharedDependencyType deptype,
int count);
+static void shdepReassignOwned_Owner(Form_pg_shdepend sdepForm, Oid newrole);
+static void shdepReassignOwned_InitAcl(Form_pg_shdepend sdepForm,
+ Oid oldrole, Oid newrole);
/*
@@ -345,10 +348,11 @@ changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
AuthIdRelationId, newOwnerId,
SHARED_DEPENDENCY_ACL);
- /* The same applies to SHARED_DEPENDENCY_INITACL */
- shdepDropDependency(sdepRel, classId, objectId, 0, true,
- AuthIdRelationId, newOwnerId,
- SHARED_DEPENDENCY_INITACL);
+ /*
+ * However, nothing need be done about SHARED_DEPENDENCY_INITACL entries,
+ * since those exist whether or not the role is the object's owner, and
+ * ALTER OWNER does not modify the underlying pg_init_privs entry.
+ */
table_close(sdepRel, RowExclusiveLock);
}
@@ -500,16 +504,18 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
* Update the pg_shdepend info for a pg_init_privs entry.
*
* Exactly like updateAclDependencies, except we are considering a
- * pg_init_privs ACL for the specified object.
+ * pg_init_privs ACL for the specified object. Since recording of
+ * pg_init_privs role dependencies is the same for owners and non-owners,
+ * we do not need an ownerId argument.
*/
void
updateInitAclDependencies(Oid classId, Oid objectId, int32 objsubId,
- Oid ownerId,
int noldmembers, Oid *oldmembers,
int nnewmembers, Oid *newmembers)
{
updateAclDependenciesWorker(classId, objectId, objsubId,
- ownerId, SHARED_DEPENDENCY_INITACL,
+ InvalidOid, /* ownerId will not be consulted */
+ SHARED_DEPENDENCY_INITACL,
noldmembers, oldmembers,
nnewmembers, newmembers);
}
@@ -542,11 +548,13 @@ updateAclDependenciesWorker(Oid classId, Oid objectId, int32 objsubId,
Oid roleid = newmembers[i];
/*
- * Skip the owner: he has an OWNER shdep entry instead. (This is
- * not just a space optimization; it makes ALTER OWNER easier. See
- * notes in changeDependencyOnOwner.)
+ * For SHARED_DEPENDENCY_ACL entries, skip the owner: she has an
+ * OWNER shdep entry instead. (This is not just a space
+ * optimization; it makes ALTER OWNER easier. See notes in
+ * changeDependencyOnOwner.) But for INITACL entries, we record
+ * the owner too.
*/
- if (roleid == ownerId)
+ if (deptype == SHARED_DEPENDENCY_ACL && roleid == ownerId)
continue;
/* Skip pinned roles; they don't need dependency entries */
@@ -563,8 +571,8 @@ updateAclDependenciesWorker(Oid classId, Oid objectId, int32 objsubId,
{
Oid roleid = oldmembers[i];
- /* Skip the owner, same as above */
- if (roleid == ownerId)
+ /* Skip the owner for ACL entries, same as above */
+ if (deptype == SHARED_DEPENDENCY_ACL && roleid == ownerId)
continue;
/* Skip pinned roles */
@@ -1476,6 +1484,13 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
}
break;
case SHARED_DEPENDENCY_INITACL:
+
+ /*
+ * Any mentions of the role that remain in pg_init_privs
+ * entries are just dropped. This is the same policy as
+ * we apply to regular ACLs.
+ */
+
/* Shouldn't see a role grant here */
Assert(sdepForm->classid != AuthMemRelationId);
RemoveRoleFromInitPriv(roleid,
@@ -1577,12 +1592,8 @@ shdepReassignOwned(List *roleids, Oid newrole)
sdepForm->dbid != InvalidOid)
continue;
- /* We leave non-owner dependencies alone */
- if (sdepForm->deptype != SHARED_DEPENDENCY_OWNER)
- continue;
-
/*
- * The various ALTER OWNER routines tend to leak memory in
+ * The various DDL routines called here tend to leak memory in
* CurrentMemoryContext. That's not a problem when they're only
* called once per command; but in this usage where we might be
* touching many objects, it can amount to a serious memory leak.
@@ -1593,81 +1604,23 @@ shdepReassignOwned(List *roleids, Oid newrole)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(cxt);
- /* Issue the appropriate ALTER OWNER call */
- switch (sdepForm->classid)
+ /* Perform the appropriate processing */
+ switch (sdepForm->deptype)
{
- case TypeRelationId:
- AlterTypeOwner_oid(sdepForm->objid, newrole, true);
- break;
-
- case NamespaceRelationId:
- AlterSchemaOwner_oid(sdepForm->objid, newrole);
- break;
-
- case RelationRelationId:
-
- /*
- * Pass recursing = true so that we don't fail on indexes,
- * owned sequences, etc when we happen to visit them
- * before their parent table.
- */
- ATExecChangeOwner(sdepForm->objid, newrole, true, AccessExclusiveLock);
- break;
-
- case DefaultAclRelationId:
-
- /*
- * Ignore default ACLs; they should be handled by DROP
- * OWNED, not REASSIGN OWNED.
- */
- break;
-
- case UserMappingRelationId:
- /* ditto */
- break;
-
- case ForeignServerRelationId:
- AlterForeignServerOwner_oid(sdepForm->objid, newrole);
- break;
-
- case ForeignDataWrapperRelationId:
- AlterForeignDataWrapperOwner_oid(sdepForm->objid, newrole);
- break;
-
- case EventTriggerRelationId:
- AlterEventTriggerOwner_oid(sdepForm->objid, newrole);
- break;
-
- case PublicationRelationId:
- AlterPublicationOwner_oid(sdepForm->objid, newrole);
+ case SHARED_DEPENDENCY_OWNER:
+ shdepReassignOwned_Owner(sdepForm, newrole);
break;
-
- case SubscriptionRelationId:
- AlterSubscriptionOwner_oid(sdepForm->objid, newrole);
+ case SHARED_DEPENDENCY_INITACL:
+ shdepReassignOwned_InitAcl(sdepForm, roleid, newrole);
break;
-
- /* Generic alter owner cases */
- case CollationRelationId:
- case ConversionRelationId:
- case OperatorRelationId:
- case ProcedureRelationId:
- case LanguageRelationId:
- case LargeObjectRelationId:
- case OperatorFamilyRelationId:
- case OperatorClassRelationId:
- case ExtensionRelationId:
- case StatisticExtRelationId:
- case TableSpaceRelationId:
- case DatabaseRelationId:
- case TSConfigRelationId:
- case TSDictionaryRelationId:
- AlterObjectOwner_internal(sdepForm->classid,
- sdepForm->objid,
- newrole);
+ case SHARED_DEPENDENCY_ACL:
+ case SHARED_DEPENDENCY_POLICY:
+ case SHARED_DEPENDENCY_TABLESPACE:
+ /* Nothing to do for these entry types */
break;
-
default:
- elog(ERROR, "unexpected classid %u", sdepForm->classid);
+ elog(ERROR, "unrecognized dependency type: %d",
+ (int) sdepForm->deptype);
break;
}
@@ -1684,3 +1637,123 @@ shdepReassignOwned(List *roleids, Oid newrole)
table_close(sdepRel, RowExclusiveLock);
}
+
+/*
+ * shdepReassignOwned_Owner
+ *
+ * shdepReassignOwned's processing of SHARED_DEPENDENCY_OWNER entries
+ */
+static void
+shdepReassignOwned_Owner(Form_pg_shdepend sdepForm, Oid newrole)
+{
+ /* Issue the appropriate ALTER OWNER call */
+ switch (sdepForm->classid)
+ {
+ case TypeRelationId:
+ AlterTypeOwner_oid(sdepForm->objid, newrole, true);
+ break;
+
+ case NamespaceRelationId:
+ AlterSchemaOwner_oid(sdepForm->objid, newrole);
+ break;
+
+ case RelationRelationId:
+
+ /*
+ * Pass recursing = true so that we don't fail on indexes, owned
+ * sequences, etc when we happen to visit them before their parent
+ * table.
+ */
+ ATExecChangeOwner(sdepForm->objid, newrole, true, AccessExclusiveLock);
+ break;
+
+ case DefaultAclRelationId:
+
+ /*
+ * Ignore default ACLs; they should be handled by DROP OWNED, not
+ * REASSIGN OWNED.
+ */
+ break;
+
+ case UserMappingRelationId:
+ /* ditto */
+ break;
+
+ case ForeignServerRelationId:
+ AlterForeignServerOwner_oid(sdepForm->objid, newrole);
+ break;
+
+ case ForeignDataWrapperRelationId:
+ AlterForeignDataWrapperOwner_oid(sdepForm->objid, newrole);
+ break;
+
+ case EventTriggerRelationId:
+ AlterEventTriggerOwner_oid(sdepForm->objid, newrole);
+ break;
+
+ case PublicationRelationId:
+ AlterPublicationOwner_oid(sdepForm->objid, newrole);
+ break;
+
+ case SubscriptionRelationId:
+ AlterSubscriptionOwner_oid(sdepForm->objid, newrole);
+ break;
+
+ /* Generic alter owner cases */
+ case CollationRelationId:
+ case ConversionRelationId:
+ case OperatorRelationId:
+ case ProcedureRelationId:
+ case LanguageRelationId:
+ case LargeObjectRelationId:
+ case OperatorFamilyRelationId:
+ case OperatorClassRelationId:
+ case ExtensionRelationId:
+ case StatisticExtRelationId:
+ case TableSpaceRelationId:
+ case DatabaseRelationId:
+ case TSConfigRelationId:
+ case TSDictionaryRelationId:
+ AlterObjectOwner_internal(sdepForm->classid,
+ sdepForm->objid,
+ newrole);
+ break;
+
+ default:
+ elog(ERROR, "unexpected classid %u", sdepForm->classid);
+ break;
+ }
+}
+
+/*
+ * shdepReassignOwned_InitAcl
+ *
+ * shdepReassignOwned's processing of SHARED_DEPENDENCY_INITACL entries
+ */
+static void
+shdepReassignOwned_InitAcl(Form_pg_shdepend sdepForm, Oid oldrole, Oid newrole)
+{
+ /*
+ * Currently, REASSIGN OWNED replaces mentions of oldrole with newrole in
+ * pg_init_privs entries, just as it does in the object's regular ACL.
+ * This is less than ideal, since pg_init_privs ought to retain a
+ * historical record of the situation at the end of CREATE EXTENSION.
+ * However, there are two big stumbling blocks to doing something
+ * different:
+ *
+ * 1. If we don't replace the references, what is to happen if the old
+ * role gets dropped? (DROP OWNED's current answer is to just delete the
+ * pg_init_privs entry, which is surely ahistorical.)
+ *
+ * 2. It's unlikely that pg_dump will cope nicely with pg_init_privs
+ * entries that are based on a different owner than the object now has ---
+ * the more so given that pg_init_privs doesn't record the original owner
+ * explicitly. (This problem actually exists anyway given that a bare
+ * ALTER OWNER won't update pg_init_privs, but we don't need REASSIGN
+ * OWNED making it worse.)
+ */
+ ReplaceRoleInInitPriv(oldrole, newrole,
+ sdepForm->classid,
+ sdepForm->objid,
+ sdepForm->objsubid);
+}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index dc10b4a4839..d7b39140b3d 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -1091,6 +1091,12 @@ aclupdate(const Acl *old_acl, const AclItem *mod_aip,
* The result is a modified copy; the input object is not changed.
*
* NB: caller is responsible for having detoasted the input ACL, if needed.
+ *
+ * Note: the name of this function is a bit of a misnomer, since it will
+ * happily make the specified role substitution whether the old role is
+ * really the owner of the parent object or merely mentioned in its ACL.
+ * But the vast majority of callers use it in connection with ALTER OWNER
+ * operations, so we'll keep the name.
*/
Acl *
aclnewowner(const Acl *old_acl, Oid oldOwnerId, Oid newOwnerId)