diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml index ec6d5f1dcf6..33c78aef748 100644 --- a/doc/src/sgml/ref/alter_subscription.sgml +++ b/doc/src/sgml/ref/alter_subscription.sgml @@ -53,6 +53,13 @@ ALTER SUBSCRIPTION name RENAME TO < to alter the owner, you must be able to SET ROLE to the new owning role. If the subscription has password_required=false, only superusers can modify it. + If the subscription uses a foreign server, the new owner must have + USAGE privilege on the foreign server, a user mapping + for the new owner or for PUBLIC must exist, and the + connection string generated for the new owner must be valid. If the new + owner is not a superuser and the subscription has + password_required=true, the generated connection string + must include a password. diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index de62ec49514..c05f7c5de4c 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -2746,11 +2746,12 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) /* * If the subscription uses a server, check that the new owner has USAGE - * privileges on the server and that a user mapping exists. Note: does not - * re-check the resulting connection string. + * privileges on the server, that a user mapping exists, and that the + * resulting connection string is valid for the new owner. */ if (OidIsValid(form->subserver)) { + char *conninfo; ForeignServer *server = GetForeignServer(form->subserver); aclresult = object_aclcheck(ForeignServerRelationId, server->serverid, newOwnerId, ACL_USAGE); @@ -2763,6 +2764,15 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) /* make sure a user mapping exists */ GetUserMapping(newOwnerId, server->serverid); + + conninfo = ForeignServerConnectionString(newOwnerId, server); + + /* Load the library providing us libpq calls. */ + load_file("libpqwalreceiver", false); + /* Check the connection info string. */ + walrcv_check_conninfo(conninfo, + form->subpasswordrequired && + !superuser_arg(newOwnerId)); } form->subowner = newOwnerId; diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out index 8dbfac66326..6d89cec1503 100644 --- a/src/test/regress/expected/subscription.out +++ b/src/test/regress/expected/subscription.out @@ -9,6 +9,10 @@ CREATE FUNCTION test_fdw_connection(oid, oid, internal) RETURNS text AS :'regresslib', 'test_fdw_connection' LANGUAGE C; +CREATE FUNCTION test_fdw_connection_no_password(oid, oid, internal) + RETURNS text + AS :'regresslib', 'test_fdw_connection_no_password' + LANGUAGE C; CREATE ROLE regress_subscription_user LOGIN SUPERUSER; CREATE ROLE regress_subscription_user2; CREATE ROLE regress_subscription_user3 IN ROLE pg_create_subscription; @@ -189,6 +193,18 @@ CREATE SUBSCRIPTION regress_testsub6 SERVER test_server WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications. RESET SESSION AUTHORIZATION; +GRANT USAGE ON FOREIGN SERVER test_server TO regress_subscription_user2; +CREATE USER MAPPING FOR regress_subscription_user2 SERVER test_server OPTIONS(user 'foo'); +ALTER FOREIGN DATA WRAPPER test_fdw CONNECTION test_fdw_connection_no_password; +WARNING: changing the foreign-data wrapper connection function can cause the options for dependent objects to become invalid +-- fail, new owner's generated conninfo must satisfy password_required +ALTER SUBSCRIPTION regress_testsub6 OWNER TO regress_subscription_user2; +ERROR: password is required +DETAIL: Non-superusers must provide a password in the connection string. +ALTER FOREIGN DATA WRAPPER test_fdw CONNECTION test_fdw_connection; +WARNING: changing the foreign-data wrapper connection function can cause the options for dependent objects to become invalid +DROP USER MAPPING FOR regress_subscription_user2 SERVER test_server; +REVOKE USAGE ON FOREIGN SERVER test_server FROM regress_subscription_user2; REVOKE USAGE ON FOREIGN SERVER test_server FROM regress_subscription_user3; SET SESSION AUTHORIZATION regress_subscription_user3; -- ok, lacks USAGE on test_server, but replacing connection anyway @@ -231,6 +247,7 @@ HINT: Use DROP ... CASCADE to drop the dependent objects too. ALTER FOREIGN DATA WRAPPER test_fdw NO CONNECTION; WARNING: removing the foreign-data wrapper connection function will cause dependent subscriptions to fail DROP FUNCTION test_fdw_connection(oid, oid, internal); +DROP FUNCTION test_fdw_connection_no_password(oid, oid, internal); DROP FOREIGN DATA WRAPPER test_fdw; -- fail - invalid connection string during ALTER ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar'; diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 90bb2a7e881..3cc3756a81a 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -742,6 +742,15 @@ test_fdw_connection(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text("dbname=regress_doesnotexist user=doesnotexist password=secret")); } +PG_FUNCTION_INFO_V1(test_fdw_connection_no_password); +Datum +test_fdw_connection_no_password(PG_FUNCTION_ARGS) +{ + /* Ensure the test fails if no valid user mapping exists. */ + GetUserMapping(PG_GETARG_OID(0), PG_GETARG_OID(1)); + PG_RETURN_TEXT_P(cstring_to_text("dbname=regress_doesnotexist user=doesnotexist")); +} + PG_FUNCTION_INFO_V1(is_catalog_text_unique_index_oid); Datum is_catalog_text_unique_index_oid(PG_FUNCTION_ARGS) diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql index 05533d66675..cfee0b41224 100644 --- a/src/test/regress/sql/subscription.sql +++ b/src/test/regress/sql/subscription.sql @@ -12,6 +12,10 @@ CREATE FUNCTION test_fdw_connection(oid, oid, internal) RETURNS text AS :'regresslib', 'test_fdw_connection' LANGUAGE C; +CREATE FUNCTION test_fdw_connection_no_password(oid, oid, internal) + RETURNS text + AS :'regresslib', 'test_fdw_connection_no_password' + LANGUAGE C; CREATE ROLE regress_subscription_user LOGIN SUPERUSER; CREATE ROLE regress_subscription_user2; @@ -136,6 +140,17 @@ CREATE SUBSCRIPTION regress_testsub6 SERVER test_server PUBLICATION testpub WITH (slot_name = 'dummy', connect = false); RESET SESSION AUTHORIZATION; +GRANT USAGE ON FOREIGN SERVER test_server TO regress_subscription_user2; +CREATE USER MAPPING FOR regress_subscription_user2 SERVER test_server OPTIONS(user 'foo'); +ALTER FOREIGN DATA WRAPPER test_fdw CONNECTION test_fdw_connection_no_password; + +-- fail, new owner's generated conninfo must satisfy password_required +ALTER SUBSCRIPTION regress_testsub6 OWNER TO regress_subscription_user2; + +ALTER FOREIGN DATA WRAPPER test_fdw CONNECTION test_fdw_connection; +DROP USER MAPPING FOR regress_subscription_user2 SERVER test_server; +REVOKE USAGE ON FOREIGN SERVER test_server FROM regress_subscription_user2; + REVOKE USAGE ON FOREIGN SERVER test_server FROM regress_subscription_user3; SET SESSION AUTHORIZATION regress_subscription_user3; @@ -182,6 +197,7 @@ DROP FUNCTION test_fdw_connection(oid, oid, internal); ALTER FOREIGN DATA WRAPPER test_fdw NO CONNECTION; DROP FUNCTION test_fdw_connection(oid, oid, internal); +DROP FUNCTION test_fdw_connection_no_password(oid, oid, internal); DROP FOREIGN DATA WRAPPER test_fdw;