Support default ACLs in get_object_address

In the spirit of 890192e99a, 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.
This commit is contained in:
Alvaro Herrera 2015-03-11 19:23:47 -03:00
parent d4d7777548
commit 4464303405
7 changed files with 158 additions and 11 deletions

View File

@ -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;

View File

@ -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:

View File

@ -1239,6 +1239,7 @@ typedef enum ObjectType
OBJECT_CONVERSION,
OBJECT_DATABASE,
OBJECT_DEFAULT,
OBJECT_DEFACL,
OBJECT_DOMAIN,
OBJECT_DOMCONSTRAINT,
OBJECT_EVENT_TRIGGER,

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;