Add has_largeobject_privilege function.
authorFujii Masao <[email protected]>
Thu, 12 Sep 2024 12:48:58 +0000 (21:48 +0900)
committerFujii Masao <[email protected]>
Thu, 12 Sep 2024 12:51:26 +0000 (21:51 +0900)
This function checks whether a user has specific privileges on a large object,
identified by OID. The user can be provided by name, OID,
or default to the current user. If the specified large object doesn't exist,
the function returns NULL. It raises an error for a non-existent user name.
This behavior is basically consistent with other privilege inquiry functions
like has_table_privilege.

Bump catalog version.

Author: Yugo Nagata
Reviewed-by: Fujii Masao
Discussion: https://postgr.es/m/20240702163444.ab586f6075e502eb84f11b1a@sranhm.sraoss.co.jp

doc/src/sgml/func.sgml
src/backend/utils/adt/acl.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/test/regress/expected/privileges.out
src/test/regress/sql/privileges.sql

index 1370440fae29ad99d35a5c17c63a931e551f26e3..e468ee62ff44bfddd363acb7f78b65206cde388d 100644 (file)
@@ -25117,6 +25117,24 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>has_largeobject_privilege</primary>
+        </indexterm>
+        <function>has_largeobject_privilege</function> (
+          <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+          <parameter>largeobject</parameter> <type>oid</type>,
+          <parameter>privilege</parameter> <type>text</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does user have privilege for large object?
+        Allowable privilege types are
+        <literal>SELECT</literal> and <literal>UPDATE</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
index 4ad222b8d10b5d3bb6b703884ed0c33ed93ab721..2a716cc6b7f376337ec9cbffbf68dcb37cb1875e 100644 (file)
@@ -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.
index d09e47d05fa088eb0ba3f6b09f5974d64184ea85..92ed2f927e0a4260fcd0c0acfee080b9fe0de030 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202409121
+#define CATALOG_VERSION_NO 202409122
 
 #endif
index 9c4f8b582600af923a7b42fa86e2eb39a6e49f1b..53a081ed886d76fd503767453af8bca132c1328a 100644 (file)
   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' },
index 430f09711498380f36ab21db7c9be7908109c787..1d903babd33e7d79c16e9d95df4403c997e5ee61 100644 (file)
@@ -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 
 --------
index e55b32f9d45b5919a7c0830230c601d3d2de4bdd..3f54b0f8f0584bc21518c10f91cc8656f8ede09a 100644 (file)
@@ -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);