From 89e8b74439f2834b76c5b8939f4c42b274036c40 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 13 Feb 2017 15:42:29 -0500 Subject: [PATCH 4/6] Add CREATE SUBSCRIPTION privilege on databases This new privilege allows the creation of subscriptions. This was previously only allowed for superusers. --- doc/src/sgml/logical-replication.sgml | 3 ++- doc/src/sgml/ref/create_subscription.sgml | 6 +++++ doc/src/sgml/ref/grant.sgml | 12 +++++++++- src/backend/catalog/aclchk.c | 4 ++++ src/backend/commands/subscriptioncmds.c | 38 +++++++++++++++++++----------- src/backend/parser/gram.y | 6 +++++ src/backend/utils/adt/acl.c | 9 +++++++ src/bin/pg_dump/dumputils.c | 2 ++ src/include/nodes/parsenodes.h | 3 ++- src/include/utils/acl.h | 5 ++-- src/test/regress/expected/subscription.out | 14 ++++++++++- src/test/regress/sql/subscription.sql | 14 ++++++++++- 12 files changed, 95 insertions(+), 21 deletions(-) diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index b6636d76b1..98411af0e6 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -315,7 +315,8 @@ Security - To create a subscription, the user must be a superuser. + To create a subscription, the user must have the CREATE + SUBSCRIPTION privilege in the database. diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml index 59e5ad00c8..830c612fad 100644 --- a/doc/src/sgml/ref/create_subscription.sgml +++ b/doc/src/sgml/ref/create_subscription.sgml @@ -131,6 +131,12 @@ Parameters Notes + To create a subscription, the invoking user must have the + CREATE SUBSCRIPTION privilege for the current database. (Of + course, superusers bypass this check.) + + + See for details on how to configure access control between the subscription and the publication instance. diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index 996d183561..f4f9afe7da 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -38,7 +38,7 @@ | ALL SEQUENCES IN SCHEMA schema_name [, ...] } TO role_specification [, ...] [ WITH GRANT OPTION ] -GRANT { { CREATE | CONNECT | TEMPORARY | TEMP } [, ...] | ALL [ PRIVILEGES ] } +GRANT { { CREATE | CONNECT | TEMPORARY | TEMP | CREATE SUBSCRIPTION } [, ...] | ALL [ PRIVILEGES ] } ON DATABASE database_name [, ...] TO role_specification [, ...] [ WITH GRANT OPTION ] @@ -310,6 +310,15 @@ GRANT on Database Objects + CREATE SUBSCRIPTION + + + For databases, allows new subscriptions to be created within the database. + + + + + CONNECT @@ -553,6 +562,7 @@ Notes X -- EXECUTE U -- USAGE C -- CREATE + S -- CREATE SUBSCRIPTION c -- CONNECT T -- TEMPORARY arwdDxtp -- ALL PRIVILEGES (for tables, varies for other objects) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 20ab6d5b0d..f38e4802bc 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -3365,6 +3365,8 @@ string_to_privilege(const char *privname) return ACL_CONNECT; if (strcmp(privname, "publication") == 0) return ACL_PUBLICATION; + if (strcmp(privname, "create subscription") == 0) + return ACL_CREATE_SUBSCRIPTION; if (strcmp(privname, "rule") == 0) return 0; /* ignore old RULE privileges */ ereport(ERROR, @@ -3404,6 +3406,8 @@ privilege_to_string(AclMode privilege) return "CONNECT"; case ACL_PUBLICATION: return "PUBLICATION"; + case ACL_CREATE_SUBSCRIPTION: + return "CREATE SUBSCRIPTION"; default: elog(ERROR, "unrecognized privilege: %d", (int) privilege); } diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index ab21e64b48..ed2a294c64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -25,6 +25,7 @@ #include "catalog/pg_type.h" #include "catalog/pg_subscription.h" +#include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/event_trigger.h" #include "commands/subscriptioncmds.h" @@ -220,11 +221,13 @@ CreateSubscription(CreateSubscriptionStmt *stmt) char originname[NAMEDATALEN]; bool create_slot; List *publications; + AclResult aclresult; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to create subscriptions")))); + /* must have CREATE SUBSCRIPTION privilege on database */ + aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE_SUBSCRIPTION); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_DATABASE, + get_database_name(MyDatabaseId)); rel = heap_open(SubscriptionRelationId, RowExclusiveLock); @@ -575,17 +578,24 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) if (form->subowner == newOwnerId) return; - if (!pg_subscription_ownercheck(HeapTupleGetOid(tup), GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION, - NameStr(form->subname)); + if (!superuser()) + { + AclResult aclresult; - /* New owner must be a superuser */ - if (!superuser_arg(newOwnerId)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied to change owner of subscription \"%s\"", - NameStr(form->subname)), - errhint("The owner of an subscription must be a superuser."))); + /* Must be owner */ + if (!pg_subscription_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION, + NameStr(form->subname)); + + /* Must be able to become new owner */ + check_is_member_of_role(GetUserId(), newOwnerId); + + /* New owner must have CREATE SUBSCRIPTION privilege on database */ + aclresult = pg_database_aclcheck(MyDatabaseId, newOwnerId, ACL_CREATE_SUBSCRIPTION); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_DATABASE, + get_database_name(MyDatabaseId)); + } form->subowner = newOwnerId; CatalogTupleUpdate(rel, &tup->t_self, tup); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2f05264e84..55a5d0a7c7 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -6777,6 +6777,12 @@ privilege: SELECT opt_column_list n->cols = $2; $$ = n; } + | CREATE ColId + { + AccessPriv *n = makeNode(AccessPriv); + n->priv_name = psprintf("create %s", $2); + $$ = n; + } ; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index e84c8d80c7..7f4513117a 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -320,6 +320,9 @@ aclparse(const char *s, AclItem *aip) case ACL_PUBLICATION_CHR: read = ACL_PUBLICATION; break; + case ACL_CREATE_SUBSCRIPTION_CHR: + read = ACL_CREATE_SUBSCRIPTION; + break; case 'R': /* ignore old RULE privileges */ read = 0; break; @@ -1640,6 +1643,8 @@ convert_priv_string(text *priv_type_text) return ACL_CONNECT; if (pg_strcasecmp(priv_type, "PUBLICATION") == 0) return ACL_PUBLICATION; + if (pg_strcasecmp(priv_type, "CREATE SUBSCRIPTION") == 0) + return ACL_CREATE_SUBSCRIPTION; if (pg_strcasecmp(priv_type, "RULE") == 0) return 0; /* ignore old RULE privileges */ @@ -1738,6 +1743,8 @@ convert_aclright_to_string(int aclright) return "CONNECT"; case ACL_PUBLICATION: return "PUBLICATION"; + case ACL_CREATE_SUBSCRIPTION: + return "CREATE SUBSCRIPTION"; default: elog(ERROR, "unrecognized aclright: %d", aclright); return NULL; @@ -3075,6 +3082,8 @@ convert_database_priv_string(text *priv_type_text) {"TEMP WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)}, {"CONNECT", ACL_CONNECT}, {"CONNECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CONNECT)}, + {"CREATE SUBSCRIPTION", ACL_CREATE_SUBSCRIPTION}, + {"CREATE SUBSCRIPTION WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_SUBSCRIPTION)}, {NULL, 0} }; diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 8b6d70f2cb..00c6b1b411 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -534,6 +534,8 @@ do { \ CONVERT_PRIV('C', "CREATE"); CONVERT_PRIV('c', "CONNECT"); CONVERT_PRIV('T', "TEMPORARY"); + if (remoteVersion >= 100000) + CONVERT_PRIV('S', "CREATE SUBSCRIPTION"); } else if (strcmp(type, "TABLESPACE") == 0) CONVERT_PRIV('C', "CREATE"); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 840e443009..488aec08ce 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -76,7 +76,8 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ #define ACL_CREATE_TEMP (1<<10) /* for databases */ #define ACL_CONNECT (1<<11) /* for databases */ #define ACL_PUBLICATION (1<<12) -#define N_ACL_RIGHTS 13 /* 1 plus the last 1<