diff options
Diffstat (limited to 'src/bin/pg_dump/dumputils.c')
-rw-r--r-- | src/bin/pg_dump/dumputils.c | 274 |
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"); + } +} |