On 07/26/2015 07:59 AM, Joe Conway wrote:
> On 07/26/2015 07:19 AM, Dean Rasheed wrote:
>> Attached is an updated patch (still needs some docs for the functions).
> 
> Thanks for that. I'll add the docs.

Documentation added. Also added comment to check_enable_rls about
passing InvalidOid versus GetUserId().

I believe this is ready to go -- any other comments?

Joe

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 98d1497..fd82ea4 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SET search_path TO <replaceable>schema</
*** 15259,15264 ****
--- 15259,15270 ----
         <entry><type>boolean</type></entry>
         <entry>does current user have privilege for role</entry>
        </row>
+       <row>
+        <entry><literal><function>row_security_active</function>(<parameter>table</parameter>)</literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>does current user have row level security active for table</entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
*************** SET search_path TO <replaceable>schema</
*** 15299,15304 ****
--- 15305,15313 ----
     <indexterm>
      <primary>pg_has_role</primary>
     </indexterm>
+    <indexterm>
+     <primary>row_security_active</primary>
+    </indexterm>
  
     <para>
      <function>has_table_privilege</function> checks whether a user
*************** SELECT has_function_privilege('joeuser',
*** 15462,15467 ****
--- 15471,15483 ----
      are immediately available without doing <command>SET ROLE</>.
     </para>
  
+    <para>
+     <function>row_security_active</function> checks whether row level
+     security is active for the specified table in the context of the
+     <function>current_user</function> and environment. The table can
+     be specified by name or by OID.
+    </para>
+ 
    <para>
     <xref linkend="functions-info-schema-table"> shows functions that
     determine whether a certain object is <firstterm>visible</> in the
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e82a53a..c0bd6fa 100644
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
*************** CREATE VIEW pg_indexes AS
*** 150,156 ****
           LEFT JOIN pg_tablespace T ON (T.oid = I.reltablespace)
      WHERE C.relkind IN ('r', 'm') AND I.relkind = 'i';
  
! CREATE VIEW pg_stats AS
      SELECT
          nspname AS schemaname,
          relname AS tablename,
--- 150,156 ----
           LEFT JOIN pg_tablespace T ON (T.oid = I.reltablespace)
      WHERE C.relkind IN ('r', 'm') AND I.relkind = 'i';
  
! CREATE VIEW pg_stats WITH (security_barrier) AS
      SELECT
          nspname AS schemaname,
          relname AS tablename,
*************** CREATE VIEW pg_stats AS
*** 211,217 ****
      FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid)
           JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum)
           LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
!     WHERE NOT attisdropped AND has_column_privilege(c.oid, a.attnum, 'select');
  
  REVOKE ALL on pg_statistic FROM public;
  
--- 211,219 ----
      FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid)
           JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum)
           LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
!     WHERE NOT attisdropped
!     AND has_column_privilege(c.oid, a.attnum, 'select')
!     AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
  
  REVOKE ALL on pg_statistic FROM public;
  
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index aaf0061..2386cf0 100644
*** a/src/backend/rewrite/rowsecurity.c
--- b/src/backend/rewrite/rowsecurity.c
*************** get_row_security_policies(Query *root, C
*** 107,113 ****
  
  	Relation	rel;
  	Oid			user_id;
- 	int			sec_context;
  	int			rls_status;
  	bool		defaultDeny = false;
  
--- 107,112 ----
*************** get_row_security_policies(Query *root, C
*** 117,138 ****
  	*hasRowSecurity = false;
  	*hasSubLinks = false;
  
! 	/* This is just to get the security context */
! 	GetUserIdAndSecContext(&user_id, &sec_context);
  
  	/* Switch to checkAsUser if it's set */
  	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
  
- 	/*
- 	 * If this is not a normal relation, or we have been told to explicitly
- 	 * skip RLS (perhaps because this is an FK check) then just return
- 	 * immediately.
- 	 */
- 	if (rte->relid < FirstNormalObjectId
- 		|| rte->relkind != RELKIND_RELATION
- 		|| (sec_context & SECURITY_ROW_LEVEL_DISABLED))
- 		return;
- 
  	/* Determine the state of RLS for this, pass checkAsUser explicitly */
  	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
  
--- 116,128 ----
  	*hasRowSecurity = false;
  	*hasSubLinks = false;
  
! 	/* If this is not a normal relation, just return immediately */
! 	if (rte->relkind != RELKIND_RELATION)
! 		return;
  
  	/* Switch to checkAsUser if it's set */
  	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
  
  	/* Determine the state of RLS for this, pass checkAsUser explicitly */
  	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
  
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index e6808e7..525794f 100644
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
*************** CreateCachedPlan(Node *raw_parse_tree,
*** 153,160 ****
  	CachedPlanSource *plansource;
  	MemoryContext source_context;
  	MemoryContext oldcxt;
- 	Oid			user_id;
- 	int			security_context;
  
  	Assert(query_string != NULL);		/* required as of 8.4 */
  
--- 153,158 ----
*************** CreateCachedPlan(Node *raw_parse_tree,
*** 177,184 ****
  	 */
  	oldcxt = MemoryContextSwitchTo(source_context);
  
- 	GetUserIdAndSecContext(&user_id, &security_context);
- 
  	plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
  	plansource->magic = CACHEDPLANSOURCE_MAGIC;
  	plansource->raw_parse_tree = copyObject(raw_parse_tree);
--- 175,180 ----
*************** CreateCachedPlan(Node *raw_parse_tree,
*** 208,215 ****
  	plansource->total_custom_cost = 0;
  	plansource->num_custom_plans = 0;
  	plansource->hasRowSecurity = false;
! 	plansource->rowSecurityDisabled
! 		= (security_context & SECURITY_ROW_LEVEL_DISABLED) != 0;
  	plansource->row_security_env = row_security;
  	plansource->planUserId = InvalidOid;
  
--- 204,210 ----
  	plansource->total_custom_cost = 0;
  	plansource->num_custom_plans = 0;
  	plansource->hasRowSecurity = false;
! 	plansource->rowSecurityDisabled = InRowLevelSecurityDisabled();
  	plansource->row_security_env = row_security;
  	plansource->planUserId = InvalidOid;
  
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index acc4752..ac3e764 100644
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
*************** GetAuthenticatedUserId(void)
*** 341,347 ****
   * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID
   * and the SecurityRestrictionContext flags.
   *
!  * Currently there are two valid bits in SecurityRestrictionContext:
   *
   * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation
   * that is temporarily changing CurrentUserId via these functions.  This is
--- 341,347 ----
   * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID
   * and the SecurityRestrictionContext flags.
   *
!  * Currently there are three valid bits in SecurityRestrictionContext:
   *
   * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation
   * that is temporarily changing CurrentUserId via these functions.  This is
*************** GetAuthenticatedUserId(void)
*** 359,364 ****
--- 359,367 ----
   * where the called functions are really supposed to be side-effect-free
   * anyway, such as VACUUM/ANALYZE/REINDEX.
   *
+  * SECURITY_ROW_LEVEL_DISABLED indicates that we are inside an operation that
+  * needs to bypass row level security checks, for example FK checks.
+  *
   * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current
   * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require
   * the new value to be valid.  In fact, these routines had better not
*************** InSecurityRestrictedOperation(void)
*** 401,406 ****
--- 404,418 ----
  	return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0;
  }
  
+ /*
+  * InRowLevelSecurityDisabled - are we inside a RLS-disabled operation?
+  */
+ bool
+ InRowLevelSecurityDisabled(void)
+ {
+ 	return (SecurityRestrictionContext & SECURITY_ROW_LEVEL_DISABLED) != 0;
+ }
+ 
  
  /*
   * These are obsolete versions of Get/SetUserIdAndSecContext that are
diff --git a/src/backend/utils/misc/rls.c b/src/backend/utils/misc/rls.c
index 44cb374..60dbec2 100644
*** a/src/backend/utils/misc/rls.c
--- b/src/backend/utils/misc/rls.c
***************
*** 16,24 ****
--- 16,27 ----
  
  #include "access/htup.h"
  #include "access/htup_details.h"
+ #include "access/transam.h"
  #include "catalog/pg_class.h"
+ #include "catalog/namespace.h"
  #include "miscadmin.h"
  #include "utils/acl.h"
+ #include "utils/builtins.h"
  #include "utils/elog.h"
  #include "utils/rls.h"
  #include "utils/syscache.h"
*************** extern int	check_enable_rls(Oid relid, O
*** 37,43 ****
   * for the table and the plan cache needs to be invalidated if the environment
   * changes.
   *
!  * Handle checking as another role via checkAsUser (for views, etc).
   *
   * If noError is set to 'true' then we just return RLS_ENABLED instead of doing
   * an ereport() if the user has attempted to bypass RLS and they are not
--- 40,49 ----
   * for the table and the plan cache needs to be invalidated if the environment
   * changes.
   *
!  * Handle checking as another role via checkAsUser (for views, etc). Note that
!  * if *not* checking as another role, the caller should pass InvalidOid rather
!  * than GetUserId(). Otherwise the check for row_security = OFF is skipped, and
!  * so we may falsely report that RLS is active when the user has bypassed it.
   *
   * If noError is set to 'true' then we just return RLS_ENABLED instead of doing
   * an ereport() if the user has attempted to bypass RLS and they are not
*************** check_enable_rls(Oid relid, Oid checkAsU
*** 53,58 ****
--- 59,76 ----
  	bool		relrowsecurity;
  	Oid			user_id = checkAsUser ? checkAsUser : GetUserId();
  
+ 	/* Nothing to do for built-in relations */
+ 	if (relid < FirstNormalObjectId)
+ 		return RLS_NONE;
+ 
+ 	/*
+ 	 * Check if we have been told to explicitly skip RLS (perhaps because this
+ 	 * is a foreign key check)
+ 	 */
+ 	if (InRowLevelSecurityDisabled())
+ 		return RLS_NONE;
+ 
+ 	/* Check if RLS is enabled on the relation */
  	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
  	if (!HeapTupleIsValid(tuple))
  		return RLS_NONE;
*************** check_enable_rls(Oid relid, Oid checkAsU
*** 111,113 ****
--- 129,165 ----
  	/* RLS should be fully enabled for this relation. */
  	return RLS_ENABLED;
  }
+ 
+ /*
+  * row_security_active
+  *
+  * check_enable_rls wrapped as a SQL callable function except
+  * RLS_NONE_ENV and RLS_NONE are the same for this purpose.
+  */
+ Datum
+ row_security_active(PG_FUNCTION_ARGS)
+ {
+ 	/* By OID */
+ 	Oid			tableoid = PG_GETARG_OID(0);
+ 	int			rls_status;
+ 
+ 	rls_status = check_enable_rls(tableoid, InvalidOid, true);
+ 	PG_RETURN_BOOL(rls_status == RLS_ENABLED);
+ }
+ 
+ Datum
+ row_security_active_name(PG_FUNCTION_ARGS)
+ {
+ 	/* By qualified name */
+ 	text	   *tablename = PG_GETARG_TEXT_P(0);
+ 	RangeVar   *tablerel;
+ 	Oid			tableoid;
+ 	int			rls_status;
+ 
+ 	/* Look up table name.  Can't lock it - we might not have privileges. */
+ 	tablerel = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
+ 	tableoid = RangeVarGetRelid(tablerel, NoLock, false);
+ 
+ 	rls_status = check_enable_rls(tableoid, InvalidOid, true);
+ 	PG_RETURN_BOOL(rls_status == RLS_ENABLED);
+ }
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 09bf143..2563bb9 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("get progress for all replication
*** 5343,5348 ****
--- 5343,5354 ----
  #define PROVOLATILE_STABLE		's'		/* does not change within a scan */
  #define PROVOLATILE_VOLATILE	'v'		/* can change even within a scan */
  
+ /* rls */
+ DATA(insert OID = 3298 (  row_security_active	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ _null_	row_security_active _null_ _null_ _null_ ));
+ DESCR("row security for current context active on table by table oid");
+ DATA(insert OID = 3299 (  row_security_active	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "25" _null_ _null_ _null_ _null_ _null_	row_security_active_name _null_ _null_ _null_ ));
+ DESCR("row security for current context active on table by table name");
+ 
  /*
   * Symbolic values for proargmodes column.  Note that these must agree with
   * the FunctionParameterMode enum in parsenodes.h; we declare them here to
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index b539167..e0cc69f 100644
*** a/src/include/miscadmin.h
--- b/src/include/miscadmin.h
*************** extern void GetUserIdAndSecContext(Oid *
*** 305,310 ****
--- 305,311 ----
  extern void SetUserIdAndSecContext(Oid userid, int sec_context);
  extern bool InLocalUserIdChange(void);
  extern bool InSecurityRestrictedOperation(void);
+ extern bool InRowLevelSecurityDisabled(void);
  extern void GetUserIdAndContext(Oid *userid, bool *sec_def_context);
  extern void SetUserIdAndContext(Oid userid, bool sec_def_context);
  extern void InitializeSessionUserId(const char *rolename, Oid useroid);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 49caa56..fc1679e 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum set_config_by_name(PG_FUNCT
*** 1121,1126 ****
--- 1121,1130 ----
  extern Datum show_all_settings(PG_FUNCTION_ARGS);
  extern Datum show_all_file_settings(PG_FUNCTION_ARGS);
  
+ /* rls.c */
+ extern Datum row_security_active(PG_FUNCTION_ARGS);
+ extern Datum row_security_active_name(PG_FUNCTION_ARGS);
+ 
  /* lockfuncs.c */
  extern Datum pg_lock_status(PG_FUNCTION_ARGS);
  extern Datum pg_advisory_lock_int8(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index e7c242c..98e36f2 100644
*** a/src/test/regress/expected/rowsecurity.out
--- b/src/test/regress/expected/rowsecurity.out
*************** SELECT * FROM current_check;
*** 2839,2848 ****
  
  COMMIT;
  --
  -- Collation support
  --
  BEGIN;
! SET row_security = force;
  CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
  CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
  ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
--- 2839,2882 ----
  
  COMMIT;
  --
+ -- check pg_stats view filtering
+ --
+ SET row_security TO ON;
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ANALYZE current_check;
+ -- Stats visible
+ SELECT row_security_active('current_check');
+  row_security_active 
+ ---------------------
+  f
+ (1 row)
+ 
+ SELECT most_common_vals FROM pg_stats where tablename = 'current_check';
+   most_common_vals   
+ ---------------------
+  
+  
+  {rls_regress_user1}
+ (3 rows)
+ 
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ -- Stats not visible
+ SELECT row_security_active('current_check');
+  row_security_active 
+ ---------------------
+  t
+ (1 row)
+ 
+ SELECT most_common_vals FROM pg_stats where tablename = 'current_check';
+  most_common_vals 
+ ------------------
+ (0 rows)
+ 
+ --
  -- Collation support
  --
  BEGIN;
! SET row_security TO FORCE;
  CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
  CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
  ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 1e5b0b9..6206c81 100644
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_stats| SELECT n.nspname AS schemaname
*** 2061,2067 ****
       JOIN pg_class c ON ((c.oid = s.starelid)))
       JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum))))
       LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
!   WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text));
  pg_tables| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      pg_get_userbyid(c.relowner) AS tableowner,
--- 2061,2067 ----
       JOIN pg_class c ON ((c.oid = s.starelid)))
       JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum))))
       LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
!   WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
  pg_tables| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      pg_get_userbyid(c.relowner) AS tableowner,
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index e86f814..73cc020 100644
*** a/src/test/regress/sql/rowsecurity.sql
--- b/src/test/regress/sql/rowsecurity.sql
*************** SELECT * FROM current_check;
*** 1141,1150 ****
  COMMIT;
  
  --
  -- Collation support
  --
  BEGIN;
! SET row_security = force;
  CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
  CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
  ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
--- 1141,1165 ----
  COMMIT;
  
  --
+ -- check pg_stats view filtering
+ --
+ SET row_security TO ON;
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ANALYZE current_check;
+ -- Stats visible
+ SELECT row_security_active('current_check');
+ SELECT most_common_vals FROM pg_stats where tablename = 'current_check';
+ 
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ -- Stats not visible
+ SELECT row_security_active('current_check');
+ SELECT most_common_vals FROM pg_stats where tablename = 'current_check';
+ 
+ --
  -- Collation support
  --
  BEGIN;
! SET row_security TO FORCE;
  CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
  CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
  ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to