diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 1370440fae..e468ee62ff 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -25117,6 +25117,24 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute'); + + + + has_largeobject_privilege + + has_largeobject_privilege ( + user name or oid, + largeobject oid, + privilege text ) + boolean + + + Does user have privilege for large object? + Allowable privilege types are + SELECT and UPDATE. + + + diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 4ad222b8d1..2a716cc6b7 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -26,6 +26,7 @@ #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_tablespace.h" @@ -39,6 +40,7 @@ #include "lib/bloomfilter.h" #include "lib/qunique.h" #include "miscadmin.h" +#include "storage/large_object.h" #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" @@ -46,6 +48,7 @@ #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/varlena.h" @@ -124,6 +127,7 @@ static AclMode convert_tablespace_priv_string(text *priv_type_text); static Oid convert_type_name(text *typename); static AclMode convert_type_priv_string(text *priv_type_text); static AclMode convert_parameter_priv_string(text *priv_text); +static AclMode convert_largeobject_priv_string(text *priv_text); static AclMode convert_role_priv_string(text *priv_type_text); static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode); @@ -4663,6 +4667,142 @@ convert_parameter_priv_string(text *priv_text) return convert_any_priv_string(priv_text, parameter_priv_map); } +/* + * has_largeobject_privilege variants + * These are all named "has_largeobject_privilege" at the SQL level. + * They take various combinations of large object OID with + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not, or NULL if object doesn't exist. + */ + +/* + * has_lo_priv_byid + * + * Helper function to check user privileges on a large object given the + * role by Oid, large object by Oid, and privileges as AclMode. + */ +static bool +has_lo_priv_byid(Oid roleid, Oid lobjId, AclMode priv, bool *is_missing) +{ + Snapshot snapshot = NULL; + AclResult aclresult; + + if (priv & ACL_UPDATE) + snapshot = NULL; + else + snapshot = GetActiveSnapshot(); + + if (!LargeObjectExistsWithSnapshot(lobjId, snapshot)) + { + Assert(is_missing != NULL); + *is_missing = true; + return false; + } + + if (lo_compat_privileges) + return true; + + aclresult = pg_largeobject_aclcheck_snapshot(lobjId, + roleid, + priv, + snapshot); + return aclresult == ACLCHECK_OK; +} + +/* + * has_largeobject_privilege_name_id + * Check user privileges on a large object given + * name username, large object oid, and text priv name. + */ +Datum +has_largeobject_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid roleid = get_role_oid_or_public(NameStr(*username)); + Oid lobjId = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + bool is_missing = false; + bool result; + + mode = convert_largeobject_priv_string(priv_type_text); + result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing); + + if (is_missing) + PG_RETURN_NULL(); + + PG_RETURN_BOOL(result); +} + +/* + * has_largeobject_privilege_id + * Check user privileges on a large object given + * large object oid, and text priv name. + * current_user is assumed + */ +Datum +has_largeobject_privilege_id(PG_FUNCTION_ARGS) +{ + Oid lobjId = PG_GETARG_OID(0); + Oid roleid = GetUserId(); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + AclMode mode; + bool is_missing = false; + bool result; + + mode = convert_largeobject_priv_string(priv_type_text); + result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing); + + if (is_missing) + PG_RETURN_NULL(); + + PG_RETURN_BOOL(result); +} + +/* + * has_largeobject_privilege_id_id + * Check user privileges on a large object given + * roleid, large object oid, and text priv name. + */ +Datum +has_largeobject_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid lobjId = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + bool is_missing = false; + bool result; + + mode = convert_largeobject_priv_string(priv_type_text); + result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing); + + if (is_missing) + PG_RETURN_NULL(); + + PG_RETURN_BOOL(result); +} + +/* + * convert_largeobject_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_largeobject_priv_string(text *priv_type_text) +{ + static const priv_map largeobject_priv_map[] = { + {"SELECT", ACL_SELECT}, + {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)}, + {"UPDATE", ACL_UPDATE}, + {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, largeobject_priv_map); +} + /* * pg_has_role variants * These are all named "pg_has_role" at the SQL level. diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index d09e47d05f..92ed2f927e 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202409121 +#define CATALOG_VERSION_NO 202409122 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 9c4f8b5826..53a081ed88 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5369,6 +5369,19 @@ prorettype => 'bool', proargtypes => 'oid text', prosrc => 'has_any_column_privilege_id' }, +{ oid => '4551', descr => 'user privilege on large objct by username, large object oid', + proname => 'has_largeobject_privilege', procost => '10', provolatile => 's', + prorettype => 'bool', proargtypes => 'name oid text', + prosrc => 'has_largeobject_privilege_name_id' }, +{ oid => '4552', descr => 'current privilege on large objct by large object oid', + proname => 'has_largeobject_privilege', procost => '10', provolatile => 's', + prorettype => 'bool', proargtypes => 'oid text', + prosrc => 'has_largeobject_privilege_id' }, +{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid', + proname => 'has_largeobject_privilege', procost => '10', provolatile => 's', + prorettype => 'bool', proargtypes => 'oid oid text', + prosrc => 'has_largeobject_privilege_id_id' }, + { oid => '3355', descr => 'I/O', proname => 'pg_ndistinct_in', prorettype => 'pg_ndistinct', proargtypes => 'cstring', prosrc => 'pg_ndistinct_in' }, diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 430f097114..1d903babd3 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -2068,10 +2068,160 @@ SELECT lo_truncate(lo_open(2001, x'20000'::int), 10); 0 (1 row) +-- has_largeobject_privilege function +-- superuser +\c - +SELECT has_largeobject_privilege(1001, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1002, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1003, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1004, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1001, 'UPDATE'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1002, 'UPDATE'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1003, 'UPDATE'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1004, 'UPDATE'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +-- not-existing large object +SELECT has_largeobject_privilege(9999, 'SELECT'); -- NULL + has_largeobject_privilege +--------------------------- + +(1 row) + +-- non-superuser +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT has_largeobject_privilege(1001, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1002, 'SELECT'); -- false + has_largeobject_privilege +--------------------------- + f +(1 row) + +SELECT has_largeobject_privilege(1003, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1004, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1001, 'UPDATE'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false + has_largeobject_privilege +--------------------------- + f +(1 row) + +SELECT has_largeobject_privilege(1003, 'UPDATE'); -- false + has_largeobject_privilege +--------------------------- + f +(1 row) + +SELECT has_largeobject_privilege(1004, 'UPDATE'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege('regress_priv_user3', 1001, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege('regress_priv_user3', 1003, 'SELECT'); -- false + has_largeobject_privilege +--------------------------- + f +(1 row) + +SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'SELECT'); + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false + has_largeobject_privilege +--------------------------- + f +(1 row) + +SELECT has_largeobject_privilege('regress_priv_user3', 2001, 'UPDATE'); + has_largeobject_privilege +--------------------------- + t +(1 row) + -- compatibility mode in largeobject permission \c - SET lo_compat_privileges = false; -- default setting SET SESSION AUTHORIZATION regress_priv_user4; +SELECT has_largeobject_privilege(1002, 'SELECT'); -- false + has_largeobject_privilege +--------------------------- + f +(1 row) + +SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false + has_largeobject_privilege +--------------------------- + f +(1 row) + SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied ERROR: permission denied for large object 1002 SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied @@ -2091,6 +2241,18 @@ ERROR: permission denied for function lo_import \c - SET lo_compat_privileges = true; -- compatibility mode SET SESSION AUTHORIZATION regress_priv_user4; +SELECT has_largeobject_privilege(1002, 'SELECT'); -- true + has_largeobject_privilege +--------------------------- + t +(1 row) + +SELECT has_largeobject_privilege(1002, 'UPDATE'); -- true + has_largeobject_privilege +--------------------------- + t +(1 row) + SELECT loread(lo_open(1002, x'40000'::int), 32); loread -------- diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index e55b32f9d4..3f54b0f8f0 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -1324,11 +1324,50 @@ SELECT loread(lo_open(1005, x'40000'::int), 32); SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied SELECT lo_truncate(lo_open(2001, x'20000'::int), 10); +-- has_largeobject_privilege function + +-- superuser +\c - +SELECT has_largeobject_privilege(1001, 'SELECT'); +SELECT has_largeobject_privilege(1002, 'SELECT'); +SELECT has_largeobject_privilege(1003, 'SELECT'); +SELECT has_largeobject_privilege(1004, 'SELECT'); + +SELECT has_largeobject_privilege(1001, 'UPDATE'); +SELECT has_largeobject_privilege(1002, 'UPDATE'); +SELECT has_largeobject_privilege(1003, 'UPDATE'); +SELECT has_largeobject_privilege(1004, 'UPDATE'); + +-- not-existing large object +SELECT has_largeobject_privilege(9999, 'SELECT'); -- NULL + +-- non-superuser +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT has_largeobject_privilege(1001, 'SELECT'); +SELECT has_largeobject_privilege(1002, 'SELECT'); -- false +SELECT has_largeobject_privilege(1003, 'SELECT'); +SELECT has_largeobject_privilege(1004, 'SELECT'); + +SELECT has_largeobject_privilege(1001, 'UPDATE'); +SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false +SELECT has_largeobject_privilege(1003, 'UPDATE'); -- false +SELECT has_largeobject_privilege(1004, 'UPDATE'); + +SELECT has_largeobject_privilege('regress_priv_user3', 1001, 'SELECT'); +SELECT has_largeobject_privilege('regress_priv_user3', 1003, 'SELECT'); -- false +SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'SELECT'); + +SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false +SELECT has_largeobject_privilege('regress_priv_user3', 2001, 'UPDATE'); + -- compatibility mode in largeobject permission \c - SET lo_compat_privileges = false; -- default setting SET SESSION AUTHORIZATION regress_priv_user4; +SELECT has_largeobject_privilege(1002, 'SELECT'); -- false +SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false + SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); -- to be denied @@ -1342,6 +1381,9 @@ SELECT lo_import('/dev/null', 2003); -- to be denied SET lo_compat_privileges = true; -- compatibility mode SET SESSION AUTHORIZATION regress_priv_user4; +SELECT has_largeobject_privilege(1002, 'SELECT'); -- true +SELECT has_largeobject_privilege(1002, 'UPDATE'); -- true + SELECT loread(lo_open(1002, x'40000'::int), 32); SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); SELECT lo_truncate(lo_open(1002, x'20000'::int), 10);