diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index 1cdc49f..4d08abf 100644 *** a/doc/src/sgml/ref/comment.sgml --- b/doc/src/sgml/ref/comment.sgml *************** COMMENT ON *** 54,59 **** --- 54,60 ---- TEXT SEARCH TEMPLATE object_name | TRIGGER trigger_name ON table_name | TYPE object_name | + USER MAPPING FOR { user_name | USER | CURRENT_USER | PUBLIC } SERVER server_name | VIEW object_name } IS 'text' *************** COMMENT ON *** 78,83 **** --- 79,88 ---- Roles don't have owners, so the rule for COMMENT ON ROLE is that you must be superuser to comment on a superuser role, or have the CREATEROLE privilege to comment on non-superuser roles. + User mappings don't have owners, so the rule for COMMENT ON USER + MAPPING is that the owner of a foreign server can comment on user + mappings for the server for any user. In addition, user who is granted + USAGE privilege on the SERVER can set the comment on his own mapping. Of course, a superuser can comment on anything. *************** COMMENT ON *** 213,218 **** --- 218,244 ---- + user_name + + + The name of the user of the target user mapping. + CURRENT_USER and USER match the + name of the current user. PUBLIC is used to match all + present and future user names in the system. + + + + + + server_name + + + The name of the server of the target user mapping. + + + + + text *************** COMMENT ON TEXT SEARCH PARSER my_parser *** 288,293 **** --- 314,320 ---- COMMENT ON TEXT SEARCH TEMPLATE snowball IS 'Snowball stemmer'; COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for RI'; COMMENT ON TYPE complex IS 'Complex number data type'; + COMMENT ON USER MAPPING FOR PUBLIC SERVER my_server IS 'Default mapping'; COMMENT ON VIEW my_view IS 'View of departmental costs'; diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index 72ecc45..6a934d6 100644 *** a/doc/src/sgml/ref/grant.sgml --- b/doc/src/sgml/ref/grant.sgml *************** GRANT rol *** 340,346 **** For servers, this privilege enables the grantee to create, ! alter, and drop his own user's user mappings associated with that server. Also, it enables the grantee to query the options of the server and associated user mappings. --- 340,346 ---- For servers, this privilege enables the grantee to create, ! alter, drop and comment his own user's user mappings associated with that server. Also, it enables the grantee to query the options of the server and associated user mappings. diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index aa3d59d..49d4841 100644 *** a/src/backend/catalog/aclchk.c --- b/src/backend/catalog/aclchk.c *************** *** 44,50 **** --- 44,52 ---- #include "catalog/pg_type.h" #include "catalog/pg_ts_config.h" #include "catalog/pg_ts_dict.h" + #include "catalog/pg_user_mapping.h" #include "commands/dbcommands.h" + #include "commands/defrem.h" #include "commands/proclang.h" #include "commands/tablespace.h" #include "foreign/foreign.h" *************** pg_foreign_server_ownercheck(Oid srv_oid *** 4643,4648 **** --- 4645,4704 ---- } /* + * Ownership check for a user mapping (specified by OID). + * + * User mappings don't have owner, so we treat users who can execute DDLs + * on the mapping as the owner. Please see user_mapping_ddl_aclcheck() for + * detailed conditions. + * + * Note that this function issues error message and quits if the user was not + * an owner. + */ + bool + pg_user_mapping_ownercheck(Oid um_oid, Oid roleid) + { + HeapTuple tuple; + Oid user_oid; + Oid srv_oid; + NameData srvname; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + /* Retrieve oid pair of the user mapping from syscache. */ + tuple = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(um_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("user mapping with OID %u does not exist", + um_oid))); + + user_oid = ((Form_pg_user_mapping) GETSTRUCT(tuple))->umuser; + srv_oid = ((Form_pg_user_mapping) GETSTRUCT(tuple))->umserver; + + ReleaseSysCache(tuple); + + /* Retrieve name of the server from syscache. */ + tuple = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(srv_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("foreign server with OID %u does not exist", + srv_oid))); + + strcpy(NameStr(srvname), + NameStr(((Form_pg_foreign_server) GETSTRUCT(tuple))->srvname)); + + ReleaseSysCache(tuple); + + /* Assume users who can execute DDLs on the user mapping as owner. */ + user_mapping_ddl_aclcheck(user_oid, srv_oid, srvname.data); + + return true; + } + + /* * Ownership check for a database (specified by OID). */ bool diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 0d21d31..630fb60 100644 *** a/src/backend/catalog/objectaddress.c --- b/src/backend/catalog/objectaddress.c *************** *** 48,53 **** --- 48,54 ---- #include "catalog/pg_ts_parser.h" #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" + #include "catalog/pg_user_mapping.h" #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/extension.h" *************** get_object_address(ObjectType objtype, L *** 232,237 **** --- 233,254 ---- address.objectId = get_ts_config_oid(objname, false); address.objectSubId = 0; break; + case OBJECT_USER_MAPPING: + { + char *username; + char *servername; + + Assert(list_length(objname) == 2); + + username = strVal(linitial(objname)); + servername = strVal(lsecond(objname)); + address.classId = UserMappingRelationId; + address.objectId = get_user_mapping_oid(username, + servername, + false); + address.objectSubId = 0; + } + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); /* placate compiler, in case it thinks elog might return */ *************** object_exists(ObjectAddress address) *** 682,687 **** --- 699,707 ---- case ForeignServerRelationId: cache = FOREIGNSERVEROID; break; + case UserMappingRelationId: + cache = USERMAPPINGOID; + break; case TSParserRelationId: cache = TSPARSEROID; break; *************** check_object_ownership(Oid roleid, Objec *** 795,800 **** --- 815,824 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, NameListToString(objname)); break; + case OBJECT_USER_MAPPING: + /* pg_user_mapping_ownercheck() never returns on error case */ + pg_user_mapping_ownercheck(address.objectId, roleid); + break; case OBJECT_LANGUAGE: if (!pg_language_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index 13d6d88..2400fc4 100644 *** a/src/backend/commands/foreigncmds.c --- b/src/backend/commands/foreigncmds.c *************** transformGenericOptions(Oid catalogId, *** 175,181 **** /* * Convert the user mapping user name to OID */ ! static Oid GetUserOidFromMapping(const char *username, bool missing_ok) { if (!username) --- 175,181 ---- /* * Convert the user mapping user name to OID */ ! Oid GetUserOidFromMapping(const char *username, bool missing_ok) { if (!username) *************** RemoveForeignServerById(Oid srvId) *** 1014,1024 **** /* ! * Common routine to check permission for user-mapping-related DDL ! * commands. We allow server owners to operate on any mapping, and ! * users to operate on their own mapping. */ ! static void user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername) { Oid curuserid = GetUserId(); --- 1014,1026 ---- /* ! * Common routine to check permission for user-mapping-related DDL commands, ! * such as CREATE, ALTER, DROP and COMMENT ON. We allow server owners to ! * operate on any mapping, and users who have USAGE privilege on the server to ! * operate on their own mapping. Note that PUBLIC mapping can be operated by ! * only server owners. */ ! void user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername) { Oid curuserid = GetUserId(); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index cda90a6..ce4f618 100644 *** a/src/backend/foreign/foreign.c --- b/src/backend/foreign/foreign.c *************** *** 19,24 **** --- 19,25 ---- #include "catalog/pg_foreign_table.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" + #include "commands/defrem.h" #include "foreign/fdwapi.h" #include "foreign/foreign.h" #include "funcapi.h" *************** get_foreign_server_oid(const char *serve *** 538,540 **** --- 539,572 ---- errmsg("server \"%s\" does not exist", servername))); return oid; } + + /* + * get_user_mapping_oid - given a USER name and SERVER name, look up the OID + * + * If missing_ok is false, throw an error if name not found. If true, just + * return InvalidOid. + */ + Oid + get_user_mapping_oid(const char *username, const char *servername, + bool missing_ok) + { + Oid useroid; + Oid serveroid; + Oid oid; + + /* Determine umuser of the mapping */ + useroid = GetUserOidFromMapping(username, false); + + /* Determine umserver of the mapping */ + serveroid = get_foreign_server_oid(servername, false); + + oid = GetSysCacheOid2(USERMAPPINGUSERSERVER, + ObjectIdGetDatum(useroid), + ObjectIdGetDatum(serveroid)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("user mapping \"%s\" for the server does not exist", + MappingUserName(useroid)))); + return oid; + } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a22ab66..c234259 100644 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** CommentStmt: *** 4954,4959 **** --- 4954,4967 ---- n->comment = $8; $$ = (Node *) n; } + | COMMENT ON USER MAPPING FOR auth_ident SERVER name IS comment_text + { + CommentStmt *n = makeNode(CommentStmt); + n->objtype = OBJECT_USER_MAPPING; + n->objname = list_make2(makeString($6), makeString($8)); + n->comment = $10; + $$ = (Node *) n; + } ; comment_type: diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 90cb9ab..cceb034 100644 *** a/src/bin/pg_dump/pg_dump.c --- b/src/bin/pg_dump/pg_dump.c *************** dumpUserMappings(Archive *fout, *** 11197,11212 **** --- 11197,11217 ---- PQExpBuffer delq; PQExpBuffer query; PQExpBuffer tag; + PQExpBuffer label; PGresult *res; int ntups; + int i_tableoid; + int i_umid; int i_usename; int i_umoptions; int i; + CatalogId catId; q = createPQExpBuffer(); tag = createPQExpBuffer(); delq = createPQExpBuffer(); query = createPQExpBuffer(); + label = createPQExpBuffer(); /* * We read from the publicly accessible view pg_user_mappings, so as not *************** dumpUserMappings(Archive *fout, *** 11219,11225 **** selectSourceSchema("pg_catalog"); appendPQExpBuffer(query, ! "SELECT usename, " "array_to_string(ARRAY(SELECT option_name || ' ' || quote_literal(option_value) FROM pg_options_to_table(umoptions)), ', ') AS umoptions\n" "FROM pg_user_mappings " "WHERE srvid = %u", --- 11224,11232 ---- selectSourceSchema("pg_catalog"); appendPQExpBuffer(query, ! "SELECT 'pg_catalog.pg_user_mapping'::pg_catalog.regclass::oid AS tableoid, " ! "umid, " ! "usename, " "array_to_string(ARRAY(SELECT option_name || ' ' || quote_literal(option_value) FROM pg_options_to_table(umoptions)), ', ') AS umoptions\n" "FROM pg_user_mappings " "WHERE srvid = %u", *************** dumpUserMappings(Archive *fout, *** 11229,11234 **** --- 11236,11243 ---- check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); ntups = PQntuples(res); + i_tableoid = PQfnumber(res, "tableoid"); + i_umid = PQfnumber(res, "umid"); i_usename = PQfnumber(res, "usename"); i_umoptions = PQfnumber(res, "umoptions"); *************** dumpUserMappings(Archive *fout, *** 11237,11248 **** char *usename; char *umoptions; usename = PQgetvalue(res, i, i_usename); umoptions = PQgetvalue(res, i, i_umoptions); resetPQExpBuffer(q); ! appendPQExpBuffer(q, "CREATE USER MAPPING FOR %s", fmtId(usename)); ! appendPQExpBuffer(q, " SERVER %s", fmtId(servername)); if (umoptions && strlen(umoptions) > 0) appendPQExpBuffer(q, " OPTIONS (%s)", umoptions); --- 11246,11262 ---- char *usename; char *umoptions; + catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + catId.oid = atooid(PQgetvalue(res, i, i_umid)); usename = PQgetvalue(res, i, i_usename); umoptions = PQgetvalue(res, i, i_umoptions); + resetPQExpBuffer(label); + appendPQExpBuffer(label, "USER MAPPING FOR %s", fmtId(usename)); + appendPQExpBuffer(label, " SERVER %s", fmtId(servername)); + resetPQExpBuffer(q); ! appendPQExpBuffer(q, "CREATE %s", label->data); if (umoptions && strlen(umoptions) > 0) appendPQExpBuffer(q, " OPTIONS (%s)", umoptions); *************** dumpUserMappings(Archive *fout, *** 11250,11257 **** appendPQExpBuffer(q, ";\n"); resetPQExpBuffer(delq); ! appendPQExpBuffer(delq, "DROP USER MAPPING FOR %s", fmtId(usename)); ! appendPQExpBuffer(delq, " SERVER %s;\n", fmtId(servername)); resetPQExpBuffer(tag); appendPQExpBuffer(tag, "USER MAPPING %s SERVER %s", --- 11264,11270 ---- appendPQExpBuffer(q, ";\n"); resetPQExpBuffer(delq); ! appendPQExpBuffer(delq, "DROP %s", label->data); resetPQExpBuffer(tag); appendPQExpBuffer(tag, "USER MAPPING %s SERVER %s", *************** dumpUserMappings(Archive *fout, *** 11266,11275 **** --- 11279,11292 ---- q->data, delq->data, NULL, &dumpId, 1, NULL, NULL); + + /* Dump User Mapping Comments */ + dumpComment(fout, label->data, NULL, owner, catId, 0, dumpId); } PQclear(res); + destroyPQExpBuffer(label); destroyPQExpBuffer(query); destroyPQExpBuffer(delq); destroyPQExpBuffer(q); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 157ee39..60e5914 100644 *** a/src/include/commands/defrem.h --- b/src/include/commands/defrem.h *************** extern Datum transformGenericOptions(Oid *** 167,172 **** --- 167,176 ---- Datum oldOptions, List *options, Oid fdwvalidator); + extern Oid GetUserOidFromMapping(const char *username, bool missing_ok); + extern void user_mapping_ddl_aclcheck(Oid umuserid, + Oid serverid, + const char *servername); /* support routines in commands/define.c */ diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h index 2fda9e3..f94d940 100644 *** a/src/include/foreign/foreign.h --- b/src/include/foreign/foreign.h *************** extern ForeignTable *GetForeignTable(Oid *** 78,82 **** --- 78,84 ---- extern Oid get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok); extern Oid get_foreign_server_oid(const char *servername, bool missing_ok); + extern Oid get_user_mapping_oid(const char *username, const char *servername, + bool missing_ok); #endif /* FOREIGN_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index d9eac76..c464c49 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef enum ObjectType *** 1133,1138 **** --- 1133,1139 ---- OBJECT_TSPARSER, OBJECT_TSTEMPLATE, OBJECT_TYPE, + OBJECT_USER_MAPPING, OBJECT_VIEW } ObjectType; diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index b28b764..a74bad5 100644 *** a/src/include/utils/acl.h --- b/src/include/utils/acl.h *************** extern bool pg_ts_dict_ownercheck(Oid di *** 317,322 **** --- 317,323 ---- extern bool pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid); extern bool pg_foreign_data_wrapper_ownercheck(Oid srv_oid, Oid roleid); extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid); + extern bool pg_user_mapping_ownercheck(Oid um_oid, Oid roleid); extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid); extern bool has_createrole_privilege(Oid roleid); diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index c05bcab..81298f6 100644 *** a/src/test/regress/expected/foreign_data.out --- b/src/test/regress/expected/foreign_data.out *************** RESET ROLE; *** 567,572 **** --- 567,573 ---- (7 rows) -- ALTER USER MAPPING + COMMENT ON USER MAPPING FOR current_user SERVER s8 IS 'postgres on s8'; ALTER USER MAPPING FOR regress_test_missing_role SERVER s4 OPTIONS (gotcha 'true'); -- ERROR ERROR: role "regress_test_missing_role" does not exist ALTER USER MAPPING FOR user SERVER ss4 OPTIONS (gotcha 'true'); -- ERROR *************** ERROR: invalid option "username" *** 578,583 **** --- 579,586 ---- HINT: Valid options in this context are: user, password ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (DROP user, SET password 'public'); SET ROLE regress_test_role; + COMMENT ON USER MAPPING FOR regress_test_role SERVER s5 IS 'regress_test_role on s5'; + COMMENT ON USER MAPPING FOR public SERVER t1 IS 'public on t1'; ALTER USER MAPPING FOR current_user SERVER s5 OPTIONS (ADD modified '1'); ALTER USER MAPPING FOR public SERVER s4 OPTIONS (ADD modified '1'); -- ERROR ERROR: must be owner of foreign server s4 diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql index 0d12b98..068d0c9 100644 *** a/src/test/regress/sql/foreign_data.sql --- b/src/test/regress/sql/foreign_data.sql *************** RESET ROLE; *** 230,241 **** --- 230,244 ---- \deu -- ALTER USER MAPPING + COMMENT ON USER MAPPING FOR current_user SERVER s8 IS 'postgres on s8'; ALTER USER MAPPING FOR regress_test_missing_role SERVER s4 OPTIONS (gotcha 'true'); -- ERROR ALTER USER MAPPING FOR user SERVER ss4 OPTIONS (gotcha 'true'); -- ERROR ALTER USER MAPPING FOR public SERVER s5 OPTIONS (gotcha 'true'); -- ERROR ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (username 'test'); -- ERROR ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (DROP user, SET password 'public'); SET ROLE regress_test_role; + COMMENT ON USER MAPPING FOR regress_test_role SERVER s5 IS 'regress_test_role on s5'; + COMMENT ON USER MAPPING FOR public SERVER t1 IS 'public on t1'; ALTER USER MAPPING FOR current_user SERVER s5 OPTIONS (ADD modified '1'); ALTER USER MAPPING FOR public SERVER s4 OPTIONS (ADD modified '1'); -- ERROR ALTER USER MAPPING FOR public SERVER t1 OPTIONS (ADD modified '1');