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;