Hi, Currently, ALTER DEFAULT PRIVILEGE doesn't support large objects, so if we want to allow users other than the owner to use the large object, we need to grant a privilege on it every time a large object is created. One of our clients feels that this is annoying, so I would like propose to extend ALTER DEFAULT PRIVILEGE to large objects.
Here are the new actions allowed in abbreviated_grant_or_revoke; +GRANT { { SELECT | UPDATE } + [, ...] | ALL [ PRIVILEGES ] } + ON LARGE OBJECTS + TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] +REVOKE [ GRANT OPTION FOR ] + { { SELECT | UPDATE } + [, ...] | ALL [ PRIVILEGES ] } + ON LARGE OBJECTS + FROM { [ GROUP ] role_name | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] A new keyword OBJECTS is introduced for using plural form in the syntax as other supported objects. A schema name is not allowed to be specified for large objects since any large objects don't belong to a schema. The attached patch is originally proposed by Haruka Takatsuka and some fixes and tests are made by me. Regards, Yugo Nagata -- Yugo NAGATA <nag...@sraoss.co.jp>
>From fe2cb39bd83d09a052d5d63889acd0968c1817b6 Mon Sep 17 00:00:00 2001 From: Yugo Nagata <nag...@sraoss.co.jp> Date: Fri, 8 Mar 2024 17:43:43 +0900 Subject: [PATCH] Extend ALTER DEFAULT PRIVILEGES for large objects Original patch by Haruka Takatsuka, some fixes and tests by Yugo Nagata. --- doc/src/sgml/catalogs.sgml | 3 +- .../sgml/ref/alter_default_privileges.sgml | 15 ++- src/backend/catalog/aclchk.c | 21 ++++ src/backend/catalog/pg_largeobject.c | 18 ++- src/backend/parser/gram.y | 5 +- src/include/catalog/pg_default_acl.h | 1 + src/include/parser/kwlist.h | 1 + src/test/regress/expected/privileges.out | 104 +++++++++++++++++- src/test/regress/sql/privileges.sql | 47 ++++++++ 9 files changed, 208 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 2907079e2a..b8cc822aeb 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -3330,7 +3330,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l <literal>S</literal> = sequence, <literal>f</literal> = function, <literal>T</literal> = type, - <literal>n</literal> = schema + <literal>n</literal> = schema, + <literal>L</literal> = large object </para></entry> </row> diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml index 1de4c5c1b4..3b358b7a88 100644 --- a/doc/src/sgml/ref/alter_default_privileges.sgml +++ b/doc/src/sgml/ref/alter_default_privileges.sgml @@ -50,6 +50,11 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] } ON SCHEMAS TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ] +GRANT { { SELECT | UPDATE } + [, ...] | ALL [ PRIVILEGES ] } + ON LARGE OBJECTS + TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ] + REVOKE [ GRANT OPTION FOR ] { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN } [, ...] | ALL [ PRIVILEGES ] } @@ -81,6 +86,13 @@ REVOKE [ GRANT OPTION FOR ] ON SCHEMAS FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ CASCADE | RESTRICT ] + +REVOKE [ GRANT OPTION FOR ] + { { SELECT | UPDATE } + [, ...] | ALL [ PRIVILEGES ] } + ON LARGE OBJECTS + FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] </synopsis> </refsynopsisdiv> @@ -159,7 +171,8 @@ REVOKE [ GRANT OPTION FOR ] If <literal>IN SCHEMA</literal> is omitted, the global default privileges are altered. <literal>IN SCHEMA</literal> is not allowed when setting privileges - for schemas, since schemas can't be nested. + for schemas and large objects, since schemas can't be nested and + large objects don't belong to a schema. </para> </listitem> </varlistentry> diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 7abf3c2a74..41baf81a1d 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -1077,6 +1077,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s all_privileges = ACL_ALL_RIGHTS_SCHEMA; errormsg = gettext_noop("invalid privilege type %s for schema"); break; + case OBJECT_LARGEOBJECT: + all_privileges = ACL_ALL_RIGHTS_LARGEOBJECT; + errormsg = gettext_noop("invalid privilege type %s for large object"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) action->objtype); @@ -1268,6 +1272,16 @@ SetDefaultACL(InternalDefaultACL *iacls) this_privileges = ACL_ALL_RIGHTS_SCHEMA; break; + case OBJECT_LARGEOBJECT: + if (OidIsValid(iacls->nspid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg("cannot use IN SCHEMA clause when using GRANT/REVOKE ON LARGE OBJECTS"))); + objtype = DEFACLOBJ_LARGEOBJECT; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_LARGEOBJECT; + break; + default: elog(ERROR, "unrecognized object type: %d", (int) iacls->objtype); @@ -1499,6 +1513,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) case DEFACLOBJ_NAMESPACE: iacls.objtype = OBJECT_SCHEMA; break; + case DEFACLOBJ_LARGEOBJECT: + iacls.objtype = OBJECT_LARGEOBJECT; + break; default: /* Shouldn't get here */ elog(ERROR, "unexpected default ACL type: %d", @@ -4324,6 +4341,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid) defaclobjtype = DEFACLOBJ_NAMESPACE; break; + case OBJECT_LARGEOBJECT: + defaclobjtype = DEFACLOBJ_LARGEOBJECT; + break; + default: return NULL; } diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c index e235f7c5e6..578589b457 100644 --- a/src/backend/catalog/pg_largeobject.c +++ b/src/backend/catalog/pg_largeobject.c @@ -22,6 +22,7 @@ #include "catalog/pg_largeobject.h" #include "catalog/pg_largeobject_metadata.h" #include "miscadmin.h" +#include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/rel.h" @@ -41,6 +42,8 @@ LargeObjectCreate(Oid loid) Oid loid_new; Datum values[Natts_pg_largeobject_metadata]; bool nulls[Natts_pg_largeobject_metadata]; + Oid ownerId; + Acl *lomacl; pg_lo_meta = table_open(LargeObjectMetadataRelationId, RowExclusiveLock); @@ -57,11 +60,18 @@ LargeObjectCreate(Oid loid) loid_new = GetNewOidWithIndex(pg_lo_meta, LargeObjectMetadataOidIndexId, Anum_pg_largeobject_metadata_oid); + ownerId = GetUserId(); + lomacl = get_user_default_acl(OBJECT_LARGEOBJECT, ownerId, InvalidOid); values[Anum_pg_largeobject_metadata_oid - 1] = ObjectIdGetDatum(loid_new); values[Anum_pg_largeobject_metadata_lomowner - 1] - = ObjectIdGetDatum(GetUserId()); - nulls[Anum_pg_largeobject_metadata_lomacl - 1] = true; + = ObjectIdGetDatum(ownerId); + + if (lomacl != NULL) + values[Anum_pg_largeobject_metadata_lomacl - 1] + = PointerGetDatum(lomacl); + else + nulls[Anum_pg_largeobject_metadata_lomacl - 1] = true; ntup = heap_form_tuple(RelationGetDescr(pg_lo_meta), values, nulls); @@ -72,6 +82,10 @@ LargeObjectCreate(Oid loid) table_close(pg_lo_meta, RowExclusiveLock); + /* dependencies on roles mentioned in default ACL */ + recordDependencyOnNewAcl(LargeObjectRelationId, loid_new, 0, + ownerId, lomacl); + return loid_new; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e8b619926e..35535ab390 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -760,7 +760,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC - OBJECT_P OF OFF OFFSET OIDS OLD OMIT ON ONLY OPERATOR OPTION OPTIONS OR + OBJECT_P OBJECTS_P OF OFF OFFSET OIDS OLD OMIT ON ONLY OPERATOR OPTION OPTIONS OR ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER @@ -8170,6 +8170,7 @@ defacl_privilege_target: | SEQUENCES { $$ = OBJECT_SEQUENCE; } | TYPES_P { $$ = OBJECT_TYPE; } | SCHEMAS { $$ = OBJECT_SCHEMA; } + | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; } ; @@ -17788,6 +17789,7 @@ unreserved_keyword: | NOWAIT | NULLS_P | OBJECT_P + | OBJECTS_P | OF | OFF | OIDS @@ -18411,6 +18413,7 @@ bare_label_keyword: | NULLS_P | NUMERIC | OBJECT_P + | OBJECTS_P | OF | OFF | OIDS diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h index d272cdf08b..f9f002fa45 100644 --- a/src/include/catalog/pg_default_acl.h +++ b/src/include/catalog/pg_default_acl.h @@ -68,6 +68,7 @@ MAKE_SYSCACHE(DEFACLROLENSPOBJ, pg_default_acl_role_nsp_obj_index, 8); #define DEFACLOBJ_FUNCTION 'f' /* function */ #define DEFACLOBJ_TYPE 'T' /* type */ #define DEFACLOBJ_NAMESPACE 'n' /* namespace */ +#define DEFACLOBJ_LARGEOBJECT 'L' /* large object */ #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index f9a4afd472..6299c29389 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -307,6 +307,7 @@ PG_KEYWORD("nullif", NULLIF, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("nulls", NULLS_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("numeric", NUMERIC, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("object", OBJECT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("objects", OBJECTS_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("of", OF, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD, AS_LABEL) diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index eb4b762ea1..29f0775373 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -2357,11 +2357,110 @@ SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'CREATE'); -- yes ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_priv_user2; COMMIT; +-- +-- Test for default privileges on large objects. This is done in a +-- separate, rollbacked, transaction to avoid any trouble with other +-- regression sessions. +-- +\c - +SET SESSION AUTHORIZATION regress_priv_user1; +SELECT lo_create(1007); + lo_create +----------- + 1007 +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied +ERROR: permission denied for large object 1007 +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied +ERROR: permission denied for large object 1007 +BEGIN; +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public; +SELECT lo_unlink(1007); + lo_unlink +----------- + 1 +(1 row) + +SELECT lo_create(1007); + lo_create +----------- + 1007 +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok + loread +-------- + \x +(1 row) + +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied +ERROR: permission denied for large object 1007 +ROLLBACK; +BEGIN; +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2; +SELECT lo_unlink(1007); + lo_unlink +----------- + 1 +(1 row) + +SELECT lo_create(1007); + lo_create +----------- + 1007 +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok + loread +-------- + \x +(1 row) + +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok + lowrite +--------- + 4 +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2; +SELECT lo_unlink(1007); + lo_unlink +----------- + 1 +(1 row) + +SELECT lo_create(1007); + lo_create +----------- + 1007 +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok + loread +-------- + \x +(1 row) + +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied +ERROR: permission denied for large object 1007 +ROLLBACK; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON LARGE OBJECTS TO public; -- error +ERROR: cannot use IN SCHEMA clause when using GRANT/REVOKE ON LARGE OBJECTS +\c - -- Test for DROP OWNED BY with shared dependencies. This is done in a -- separate, rollbacked, transaction to avoid any trouble with other -- regression sessions. BEGIN; ALTER DEFAULT PRIVILEGES GRANT ALL ON FUNCTIONS TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON LARGE OBJECTS TO regress_priv_user2; ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; ALTER DEFAULT PRIVILEGES GRANT ALL ON SEQUENCES TO regress_priv_user2; ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO regress_priv_user2; @@ -2372,7 +2471,7 @@ SELECT count(*) FROM pg_shdepend classid = 'pg_default_acl'::regclass; count ------- - 5 + 6 (1 row) DROP OWNED BY regress_priv_user2, regress_priv_user2; @@ -2708,7 +2807,8 @@ SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3 1 1 1 -(5 rows) + 1 +(6 rows) DROP GROUP regress_priv_group1; DROP GROUP regress_priv_group2; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index eeb4c00292..e8db20573c 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -1487,11 +1487,58 @@ ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_priv_user2; COMMIT; +-- +-- Test for default privileges on large objects. This is done in a +-- separate, rollbacked, transaction to avoid any trouble with other +-- regression sessions. +-- +\c - +SET SESSION AUTHORIZATION regress_priv_user1; +SELECT lo_create(1007); +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied + +BEGIN; +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public; +SELECT lo_unlink(1007); +SELECT lo_create(1007); +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied +ROLLBACK; + +BEGIN; + +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2; +SELECT lo_unlink(1007); +SELECT lo_create(1007); +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok + +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2; +SELECT lo_unlink(1007); +SELECT lo_create(1007); +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok +SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied + +ROLLBACK; + +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON LARGE OBJECTS TO public; -- error + +\c - + -- Test for DROP OWNED BY with shared dependencies. This is done in a -- separate, rollbacked, transaction to avoid any trouble with other -- regression sessions. BEGIN; ALTER DEFAULT PRIVILEGES GRANT ALL ON FUNCTIONS TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON LARGE OBJECTS TO regress_priv_user2; ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; ALTER DEFAULT PRIVILEGES GRANT ALL ON SEQUENCES TO regress_priv_user2; ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO regress_priv_user2; -- 2.25.1