From 4464303405f1f886d63f8316386621cd7436c5d6 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Wed, 11 Mar 2015 19:23:47 -0300 Subject: [PATCH] Support default ACLs in get_object_address In the spirit of 890192e99af, this time add support for the things living in the pg_default_acl catalog. These are not really "objects", but they show up as such in event triggers. There is no "DROP DEFAULT PRIVILEGES" or similar command, so it doesn't look like the new representation given would be useful anywhere else, so I didn't try to use it outside objectaddress.c. (That might be a bug in itself, but that would be material for another commit.) Reviewed by Stephen Frost. --- src/backend/catalog/objectaddress.c | 137 +++++++++++++++++-- src/backend/commands/event_trigger.c | 1 + src/include/nodes/parsenodes.h | 1 + src/test/regress/expected/event_trigger.out | 4 + src/test/regress/expected/object_address.out | 17 ++- src/test/regress/sql/event_trigger.sql | 2 + src/test/regress/sql/object_address.sql | 7 +- 7 files changed, 158 insertions(+), 11 deletions(-) diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 67c14020e5..142bc689e9 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -522,7 +522,7 @@ ObjectTypeMap[] = /* OCLASS_USER_MAPPING */ { "user mapping", OBJECT_USER_MAPPING }, /* OCLASS_DEFACL */ - { "default acl", -1 }, /* unmapped */ + { "default acl", OBJECT_DEFACL }, /* OCLASS_EXTENSION */ { "extension", OBJECT_EXTENSION }, /* OCLASS_EVENT_TRIGGER */ @@ -557,6 +557,8 @@ static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname, List *objargs, bool missing_ok); static ObjectAddress get_object_address_usermapping(List *objname, List *objargs, bool missing_ok); +static ObjectAddress get_object_address_defacl(List *objname, List *objargs, + bool missing_ok); static const ObjectPropertyType *get_object_property_data(Oid class_id); static void getRelationDescription(StringInfo buffer, Oid relid); @@ -775,6 +777,10 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, address = get_object_address_usermapping(objname, objargs, missing_ok); break; + case OBJECT_DEFACL: + address = get_object_address_defacl(objname, objargs, + missing_ok); + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); /* placate compiler, in case it thinks elog might return */ @@ -1447,6 +1453,113 @@ get_object_address_usermapping(List *objname, List *objargs, bool missing_ok) return address; } +/* + * Find the ObjectAddress for a default ACL. + */ +static ObjectAddress +get_object_address_defacl(List *objname, List *objargs, bool missing_ok) +{ + HeapTuple tp; + Oid userid; + Oid schemaid; + char *username; + char *schema; + char objtype; + char *objtype_str; + ObjectAddress address; + + ObjectAddressSet(address, DefaultAclRelationId, InvalidOid); + + /* + * First figure out the textual attributes so that they can be used for + * error reporting. + */ + username = strVal(linitial(objname)); + if (list_length(objname) >= 2) + schema = (char *) strVal(lsecond(objname)); + else + schema = NULL; + + /* + * Decode defaclobjtype. Only first char is considered; the rest of the + * string, if any, is blissfully ignored. + */ + objtype = ((char *) strVal(linitial(objargs)))[0]; + switch (objtype) + { + case DEFACLOBJ_RELATION: + objtype_str = "tables"; + break; + case DEFACLOBJ_SEQUENCE: + objtype_str = "sequences"; + break; + case DEFACLOBJ_FUNCTION: + objtype_str = "functions"; + break; + case DEFACLOBJ_TYPE: + objtype_str = "types"; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized default ACL object type %c", objtype), + errhint("Valid object types are 'r', 'S', 'f', and 'T'."))); + } + + /* + * Look up user ID. Behave as "default ACL not found" if the user doesn't + * exist. + */ + tp = SearchSysCache1(AUTHNAME, + CStringGetDatum(username)); + if (!HeapTupleIsValid(tp)) + goto not_found; + userid = HeapTupleGetOid(tp); + ReleaseSysCache(tp); + + /* + * If a schema name was given, look up its OID. If it doesn't exist, + * behave as "default ACL not found". + */ + if (schema) + { + schemaid = get_namespace_oid(schema, true); + if (schemaid == InvalidOid) + goto not_found; + } + else + schemaid = InvalidOid; + + /* Finally, look up the pg_default_acl object */ + tp = SearchSysCache3(DEFACLROLENSPOBJ, + ObjectIdGetDatum(userid), + ObjectIdGetDatum(schemaid), + CharGetDatum(objtype)); + if (!HeapTupleIsValid(tp)) + goto not_found; + + address.objectId = HeapTupleGetOid(tp); + ReleaseSysCache(tp); + + return address; + +not_found: + if (!missing_ok) + { + if (schema) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("default ACL for user \"%s\" in schema \"%s\" on %s does not exist", + username, schema, objtype_str))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("default ACL for user \"%s\" on %s does not exist", + username, objtype_str))); + } + return address; +} + /* * Convert an array of TEXT into a List of string Values, as emitted by the * parser, which is what get_object_address uses as input. @@ -1599,6 +1712,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_OPFAMILY: case OBJECT_CAST: case OBJECT_USER_MAPPING: + case OBJECT_DEFACL: if (list_length(args) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -4024,10 +4138,8 @@ getObjectIdentityParts(const ObjectAddress *object, SysScanDesc rcscan; HeapTuple tup; Form_pg_default_acl defacl; - - /* no objname support */ - if (objname) - *objname = NIL; + char *schema; + char *username; defaclrel = heap_open(DefaultAclRelationId, AccessShareLock); @@ -4047,19 +4159,20 @@ getObjectIdentityParts(const ObjectAddress *object, defacl = (Form_pg_default_acl) GETSTRUCT(tup); + username = GetUserNameFromId(defacl->defaclrole); appendStringInfo(&buffer, "for role %s", - quote_identifier(GetUserNameFromId(defacl->defaclrole))); + quote_identifier(username)); if (OidIsValid(defacl->defaclnamespace)) { - char *schema; - schema = get_namespace_name(defacl->defaclnamespace); appendStringInfo(&buffer, " in schema %s", quote_identifier(schema)); } + else + schema = NULL; switch (defacl->defaclobjtype) { @@ -4081,6 +4194,14 @@ getObjectIdentityParts(const ObjectAddress *object, break; } + if (objname) + { + *objname = list_make1(username); + if (schema) + *objname = lappend(*objname, schema); + *objargs = list_make1(psprintf("%c", defacl->defaclobjtype)); + } + systable_endscan(rcscan); heap_close(defaclrel, AccessShareLock); break; diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 4e446bd25c..3fec57ea23 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1065,6 +1065,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_COLUMN: case OBJECT_COLLATION: case OBJECT_CONVERSION: + case OBJECT_DEFACL: case OBJECT_DEFAULT: case OBJECT_DOMAIN: case OBJECT_DOMCONSTRAINT: diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1279aca882..38ed661122 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1239,6 +1239,7 @@ typedef enum ObjectType OBJECT_CONVERSION, OBJECT_DATABASE, OBJECT_DEFAULT, + OBJECT_DEFACL, OBJECT_DOMAIN, OBJECT_DOMCONSTRAINT, OBJECT_EVENT_TRIGGER, diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index b87d503436..1dace02782 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -116,6 +116,9 @@ create server useless_server foreign data wrapper useless; NOTICE: test_event_trigger: ddl_command_end CREATE SERVER create user mapping for regression_bob server useless_server; NOTICE: test_event_trigger: ddl_command_end CREATE USER MAPPING +alter default privileges for role regression_bob + revoke delete on tables from regression_bob; +NOTICE: test_event_trigger: ddl_command_end ALTER DEFAULT PRIVILEGES -- alter owner to non-superuser should fail alter event trigger regress_event_trigger owner to regression_bob; ERROR: permission denied to change owner of event trigger "regress_event_trigger" @@ -135,6 +138,7 @@ ERROR: event trigger "regress_event_trigger" does not exist drop role regression_bob; ERROR: role "regression_bob" cannot be dropped because some objects depend on it DETAIL: owner of event trigger regress_event_trigger3 +owner of default privileges on new relations belonging to role regression_bob owner of user mapping for regression_bob on server useless_server -- cleanup before next test -- these are all OK; the second one should emit a NOTICE diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index e72abda90a..3bcbcd8b65 100644 --- a/src/test/regress/expected/object_address.out +++ b/src/test/regress/expected/object_address.out @@ -30,6 +30,8 @@ CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDU CREATE POLICY genpol ON addr_nsp.gentable; CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw; CREATE USER MAPPING FOR regtest_addr_user SERVER "integer"; +ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regtest_addr_user; +ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user REVOKE DELETE ON TABLES FROM regtest_addr_user; -- test some error cases SELECT pg_get_object_address('stone', '{}', '{}'); ERROR: unrecognized object type "stone" @@ -77,7 +79,7 @@ BEGIN ('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'), ('text search parser'), ('text search dictionary'), ('text search template'), ('text search configuration'), - ('policy'), ('user mapping') + ('policy'), ('user mapping'), ('default acl') LOOP FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}') LOOP @@ -255,6 +257,12 @@ WARNING: error for user mapping,{addr_nsp,zwei},{}: argument list length must b WARNING: error for user mapping,{addr_nsp,zwei},{integer}: user mapping for user "addr_nsp" in server "integer" does not exist WARNING: error for user mapping,{eins,zwei,drei},{}: argument list length must be exactly 1 WARNING: error for user mapping,{eins,zwei,drei},{integer}: user mapping for user "eins" in server "integer" does not exist +WARNING: error for default acl,{eins},{}: argument list length must be exactly 1 +WARNING: error for default acl,{eins},{integer}: unrecognized default ACL object type i +WARNING: error for default acl,{addr_nsp,zwei},{}: argument list length must be exactly 1 +WARNING: error for default acl,{addr_nsp,zwei},{integer}: unrecognized default ACL object type i +WARNING: error for default acl,{eins,zwei,drei},{}: argument list length must be exactly 1 +WARNING: error for default acl,{eins,zwei,drei},{integer}: unrecognized default ACL object type i -- these object types cannot be qualified names SELECT pg_get_object_address('language', '{one}', '{}'); ERROR: language "one" does not exist @@ -341,6 +349,8 @@ WITH objects (type, name, args) AS (VALUES ('foreign-data wrapper', '{addr_fdw}', '{}'), ('server', '{addr_fserv}', '{}'), ('user mapping', '{regtest_addr_user}', '{integer}'), + ('default acl', '{regtest_addr_user,public}', '{r}'), + ('default acl', '{regtest_addr_user}', '{r}'), -- extension -- event trigger ('policy', '{addr_nsp, gentable, genpol}', '{}') @@ -355,6 +365,8 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*, ORDER BY addr1.classid, addr1.objid; type | schema | name | identity | ?column? ---------------------------+------------+-------------------+----------------------------------------------------------------------+---------- + default acl | | | for role regtest_addr_user in schema public on tables | t + default acl | | | for role regtest_addr_user on tables | t type | pg_catalog | _int4 | integer[] | t type | addr_nsp | gencomptype | addr_nsp.gencomptype | t type | addr_nsp | genenum | addr_nsp.genenum | t @@ -391,11 +403,12 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*, text search parser | addr_nsp | addr_ts_prs | addr_nsp.addr_ts_prs | t text search configuration | addr_nsp | addr_ts_conf | addr_nsp.addr_ts_conf | t text search template | addr_nsp | addr_ts_temp | addr_nsp.addr_ts_temp | t -(36 rows) +(38 rows) --- --- Cleanup resources --- DROP FOREIGN DATA WRAPPER addr_fdw CASCADE; DROP SCHEMA addr_nsp CASCADE; +DROP OWNED BY regtest_addr_user; DROP USER regtest_addr_user; diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index bcfeb3a869..1b7346409c 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -110,6 +110,8 @@ drop table event_trigger_fire1; create foreign data wrapper useless; create server useless_server foreign data wrapper useless; create user mapping for regression_bob server useless_server; +alter default privileges for role regression_bob + revoke delete on tables from regression_bob; -- alter owner to non-superuser should fail alter event trigger regress_event_trigger owner to regression_bob; diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql index b714b529c8..a49f03fdf7 100644 --- a/src/test/regress/sql/object_address.sql +++ b/src/test/regress/sql/object_address.sql @@ -34,6 +34,8 @@ CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDU CREATE POLICY genpol ON addr_nsp.gentable; CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw; CREATE USER MAPPING FOR regtest_addr_user SERVER "integer"; +ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regtest_addr_user; +ALTER DEFAULT PRIVILEGES FOR ROLE regtest_addr_user REVOKE DELETE ON TABLES FROM regtest_addr_user; -- test some error cases SELECT pg_get_object_address('stone', '{}', '{}'); @@ -73,7 +75,7 @@ BEGIN ('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'), ('text search parser'), ('text search dictionary'), ('text search template'), ('text search configuration'), - ('policy'), ('user mapping') + ('policy'), ('user mapping'), ('default acl') LOOP FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}') LOOP @@ -156,6 +158,8 @@ WITH objects (type, name, args) AS (VALUES ('foreign-data wrapper', '{addr_fdw}', '{}'), ('server', '{addr_fserv}', '{}'), ('user mapping', '{regtest_addr_user}', '{integer}'), + ('default acl', '{regtest_addr_user,public}', '{r}'), + ('default acl', '{regtest_addr_user}', '{r}'), -- extension -- event trigger ('policy', '{addr_nsp, gentable, genpol}', '{}') @@ -176,4 +180,5 @@ DROP FOREIGN DATA WRAPPER addr_fdw CASCADE; DROP SCHEMA addr_nsp CASCADE; +DROP OWNED BY regtest_addr_user; DROP USER regtest_addr_user;