aboutsummaryrefslogtreecommitdiff
path: root/src/bin/pg_dump/dumputils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/pg_dump/dumputils.c')
-rw-r--r--src/bin/pg_dump/dumputils.c274
1 files changed, 247 insertions, 27 deletions
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5301d3fa541..c55a2fa14ec 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -38,6 +38,7 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
* TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
* FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
* acls: the ACL string fetched from the database
+ * racls: the ACL string of any initial-but-now-revoked privileges
* owner: username of object owner (will be passed through fmtId); can be
* NULL or empty string to indicate "no owner known"
* prefix: string to prefix to each generated command; typically empty
@@ -54,13 +55,15 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
*/
bool
buildACLCommands(const char *name, const char *subname,
- const char *type, const char *acls, const char *owner,
- const char *prefix, int remoteVersion,
+ const char *type, const char *acls, const char *racls,
+ const char *owner, const char *prefix, int remoteVersion,
PQExpBuffer sql)
{
bool ok = true;
- char **aclitems;
- int naclitems;
+ char **aclitems = NULL;
+ char **raclitems = NULL;
+ int naclitems = 0;
+ int nraclitems = 0;
int i;
PQExpBuffer grantee,
grantor,
@@ -70,18 +73,31 @@ buildACLCommands(const char *name, const char *subname,
secondsql;
bool found_owner_privs = false;
- if (strlen(acls) == 0)
+ if (strlen(acls) == 0 && strlen(racls) == 0)
return true; /* object has default permissions */
/* treat empty-string owner same as NULL */
if (owner && *owner == '\0')
owner = NULL;
- if (!parsePGArray(acls, &aclitems, &naclitems))
+ if (strlen(acls) != 0)
{
- if (aclitems)
- free(aclitems);
- return false;
+ if (!parsePGArray(acls, &aclitems, &naclitems))
+ {
+ if (aclitems)
+ free(aclitems);
+ return false;
+ }
+ }
+
+ if (strlen(racls) != 0)
+ {
+ if (!parsePGArray(racls, &raclitems, &nraclitems))
+ {
+ if (raclitems)
+ free(raclitems);
+ return false;
+ }
}
grantee = createPQExpBuffer();
@@ -90,24 +106,101 @@ buildACLCommands(const char *name, const char *subname,
privswgo = createPQExpBuffer();
/*
- * At the end, these two will be pasted together to form the result. But
- * the owner privileges need to go before the other ones to keep the
- * dependencies valid. In recent versions this is normally the case, but
- * in old versions they come after the PUBLIC privileges and that results
- * in problems if we need to run REVOKE on the owner privileges.
+ * At the end, these two will be pasted together to form the result.
+ *
+ * For older systems we use these to ensure that the owner privileges go
+ * before the other ones, as a GRANT could create the default entry for
+ * the object, which generally includes all rights for the owner. In more
+ * recent versions we normally handle this because the owner rights come
+ * first in the ACLs, but older versions might have them after the PUBLIC
+ * privileges.
+ *
+ * For 9.6 and later systems, much of this changes. With 9.6, we check
+ * the default privileges for the objects at dump time and create two sets
+ * of ACLs- "racls" which are the ACLs to REVOKE from the object (as the
+ * object may have initial privileges on it, along with any default ACLs
+ * which are not part of the current set of privileges), and regular
+ * "acls", which are the ACLs to GRANT to the object. We handle the
+ * REVOKEs first, followed by the GRANTs.
*/
firstsql = createPQExpBuffer();
secondsql = createPQExpBuffer();
/*
- * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
- * wire-in knowledge about the default public privileges for different
- * kinds of objects.
+ * For pre-9.6 systems, we always start with REVOKE ALL FROM PUBLIC, as we
+ * don't wish to make any assumptions about what the default ACLs are, and
+ * we do not collect them during the dump phase (and racls will always be
+ * the empty set, see above).
+ *
+ * For 9.6 and later, if any revoke ACLs have been provided, then include
+ * them in 'firstsql'.
+ *
+ * Revoke ACLs happen when an object starts out life with a set of
+ * privileges (eg: GRANT SELECT ON pg_class TO PUBLIC;) and the user has
+ * decided to revoke those rights. Since those objects come into being
+ * with those default privileges, we have to revoke them to match what the
+ * current state of affairs is. Note that we only started explicitly
+ * tracking such initial rights in 9.6, and prior to that all initial
+ * rights are actually handled by the simple 'REVOKE ALL .. FROM PUBLIC'
+ * case, for initdb-created objects. Prior to 9.6, we didn't handle
+ * extensions correctly, but we do now by tracking their initial
+ * privileges, in the same way we track initdb initial privileges, see
+ * pg_init_privs.
*/
- appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
- if (subname)
- appendPQExpBuffer(firstsql, "(%s)", subname);
- appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
+ if (remoteVersion < 90600)
+ {
+ Assert(nraclitems == 0);
+
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
+ }
+ else
+ {
+ /* Scan individual REVOKE ACL items */
+ for (i = 0; i < nraclitems; i++)
+ {
+ if (!parseAclItem(raclitems[i], type, name, subname, remoteVersion,
+ grantee, grantor, privs, privswgo))
+ {
+ ok = false;
+ break;
+ }
+
+ if (privs->len > 0 || privswgo->len > 0)
+ {
+ if (privs->len > 0)
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE %s ON %s %s FROM ",
+ prefix, privs->data, type, name);
+ if (grantee->len == 0)
+ appendPQExpBufferStr(firstsql, "PUBLIC;\n");
+ else if (strncmp(grantee->data, "group ",
+ strlen("group ")) == 0)
+ appendPQExpBuffer(firstsql, "GROUP %s;\n",
+ fmtId(grantee->data + strlen("group ")));
+ else
+ appendPQExpBuffer(firstsql, "%s;\n",
+ fmtId(grantee->data));
+ }
+ if (privswgo->len > 0)
+ {
+ appendPQExpBuffer(firstsql,
+ "%sREVOKE GRANT OPTION FOR %s ON %s %s FROM ",
+ prefix, privswgo->data, type, name);
+ if (grantee->len == 0)
+ appendPQExpBufferStr(firstsql, "PUBLIC");
+ else if (strncmp(grantee->data, "group ",
+ strlen("group ")) == 0)
+ appendPQExpBuffer(firstsql, "GROUP %s",
+ fmtId(grantee->data + strlen("group ")));
+ else
+ appendPQExpBufferStr(firstsql, fmtId(grantee->data));
+ }
+ }
+ }
+ }
/*
* We still need some hacking though to cover the case where new default
@@ -138,7 +231,14 @@ buildACLCommands(const char *name, const char *subname,
if (privs->len > 0 || privswgo->len > 0)
{
- if (owner
+ /*
+ * Prior to 9.6, we had to handle owner privileges in a special
+ * manner by first REVOKE'ing the rights and then GRANT'ing them
+ * after. With 9.6 and above, what we need to REVOKE and what we
+ * need to GRANT is figured out when we dump and stashed into
+ * "racls" and "acls", respectivly. See above.
+ */
+ if (remoteVersion < 90600 && owner
&& strcmp(grantee->data, owner) == 0
&& strcmp(grantor->data, owner) == 0)
{
@@ -172,7 +272,14 @@ buildACLCommands(const char *name, const char *subname,
else
{
/*
- * Otherwise can assume we are starting from no privs.
+ * For systems prior to 9.6, we can assume we are starting
+ * from no privs at this point.
+ *
+ * For 9.6 and above, at this point we have issued REVOKE
+ * statements for all initial and default privileges which are
+ * no longer present on the object (as they were passed in as
+ * 'racls') and we can simply GRANT the rights which are in
+ * 'acls'.
*/
if (grantor->len > 0
&& (!owner || strcmp(owner, grantor->data) != 0))
@@ -215,9 +322,12 @@ buildACLCommands(const char *name, const char *subname,
}
/*
- * If we didn't find any owner privs, the owner must have revoked 'em all
+ * For systems prior to 9.6, if we didn't find any owner privs, the owner
+ * must have revoked 'em all.
+ *
+ * For 9.6 and above, we handle this through the 'racls'. See above.
*/
- if (!found_owner_privs && owner)
+ if (remoteVersion < 90600 && !found_owner_privs && owner)
{
appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
if (subname)
@@ -235,7 +345,11 @@ buildACLCommands(const char *name, const char *subname,
destroyPQExpBuffer(firstsql);
destroyPQExpBuffer(secondsql);
- free(aclitems);
+ if (aclitems)
+ free(aclitems);
+
+ if (raclitems)
+ free(raclitems);
return ok;
}
@@ -275,7 +389,7 @@ buildDefaultACLCommands(const char *type, const char *nspname,
appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
result = buildACLCommands("", NULL,
- type, acls, owner,
+ type, acls, "", owner,
prefix->data, remoteVersion,
sql);
@@ -555,3 +669,109 @@ emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer,
appendPQExpBufferStr(buffer, ";\n");
}
}
+
+/*
+ * buildACLQueries
+ *
+ * Build the subqueries to extract out the correct set of ACLs to be
+ * GRANT'd and REVOKE'd for the specific kind of object, accounting for any
+ * initial privileges (from pg_init_privs) and based on if we are in binary
+ * upgrade mode or not.
+ *
+ * Also builds subqueries to extract out the set of ACLs to go from the object
+ * default privileges to the privileges in pg_init_privs, if we are in binary
+ * upgrade mode, so that those privileges can be set up and recorded in the new
+ * cluster before the regular privileges are added on top of those.
+ */
+void
+buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
+ PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery,
+ const char *acl_column, const char *acl_owner,
+ const char *obj_kind, bool binary_upgrade)
+{
+ /*
+ * To get the delta from what the permissions were at creation time
+ * (either initdb or CREATE EXTENSION) vs. what they are now, we have to
+ * look at two things:
+ *
+ * What privileges have been added, which we calculate by extracting all
+ * the current privileges (using the set of default privileges for the
+ * object type if current privileges are NULL) and then removing those
+ * which existed at creation time (again, using the set of default
+ * privileges for the object type if there were no creation time
+ * privileges).
+ *
+ * What privileges have been removed, which we calculate by extracting the
+ * privileges as they were at creation time (or the default privileges, as
+ * above), and then removing the current privileges (or the default
+ * privileges, if current privileges are NULL).
+ *
+ * As a good cross-check, both directions of these checks should result in
+ * the empty set if both the current ACL and the initial privs are NULL
+ * (meaning, in practice, that the default ACLs were there at init time
+ * and is what the current privileges are).
+ *
+ * We always perform this delta on all ACLs and expect that by the time
+ * these are run the initial privileges will be in place, even in a
+ * binary upgrade situation (see below).
+ */
+ printfPQExpBuffer(acl_subquery, "(SELECT array_agg(acl) FROM "
+ "(SELECT unnest(coalesce(%s,acldefault(%s,%s))) AS acl "
+ "EXCEPT "
+ "SELECT unnest(coalesce(pip.initprivs,acldefault(%s,%s)))) as foo)",
+ acl_column,
+ obj_kind,
+ acl_owner,
+ obj_kind,
+ acl_owner);
+
+ printfPQExpBuffer(racl_subquery, "(SELECT array_agg(acl) FROM "
+ "(SELECT unnest(coalesce(pip.initprivs,acldefault(%s,%s))) AS acl "
+ "EXCEPT "
+ "SELECT unnest(coalesce(%s,acldefault(%s,%s)))) as foo)",
+ obj_kind,
+ acl_owner,
+ acl_column,
+ obj_kind,
+ acl_owner);
+
+ /*
+ * In binary upgrade mode we don't run the extension script but instead
+ * dump out the objects independently and then recreate them. To preserve
+ * the initial privileges which were set on extension objects, we need to
+ * grab the set of GRANT and REVOKE commands necessary to get from the
+ * default privileges of an object to the initial privileges as recorded
+ * in pg_init_privs.
+ *
+ * These will then be run ahead of the regular ACL commands, which were
+ * calculated using the queries above, inside of a block which sets a flag
+ * to indicate that the backend should record the results of these GRANT
+ * and REVOKE statements into pg_init_privs. This is how we preserve the
+ * contents of that catalog across binary upgrades.
+ */
+ if (binary_upgrade)
+ {
+ printfPQExpBuffer(init_acl_subquery,
+ "CASE WHEN privtype = 'e' THEN "
+ "(SELECT array_agg(acl) FROM "
+ "(SELECT unnest(pip.initprivs) AS acl "
+ "EXCEPT "
+ "SELECT unnest(acldefault(%s,%s))) as foo) END",
+ obj_kind,
+ acl_owner);
+
+ printfPQExpBuffer(init_racl_subquery,
+ "CASE WHEN privtype = 'e' THEN "
+ "(SELECT array_agg(acl) FROM "
+ "(SELECT unnest(acldefault(%s,%s)) AS acl "
+ "EXCEPT "
+ "SELECT unnest(pip.initprivs)) as foo) END",
+ obj_kind,
+ acl_owner);
+ }
+ else
+ {
+ printfPQExpBuffer(init_acl_subquery, "NULL");
+ printfPQExpBuffer(init_racl_subquery, "NULL");
+ }
+}