<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>
<indexterm>
<primary>pg_has_role</primary>
</indexterm>
+ <indexterm>
+ <primary>row_security_active</primary>
+ </indexterm>
<para>
<function>has_table_privilege</function> checks whether a user
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
Assert(indexrelid == idxrec->indexrelid);
/* RLS check- if RLS is enabled then we don't return anything. */
- if (check_enable_rls(indrelid, GetUserId(), true) == RLS_ENABLED)
+ if (check_enable_rls(indrelid, InvalidOid, true) == RLS_ENABLED)
{
ReleaseSysCache(ht_idx);
return NULL;
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
+CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
relname AS tablename,
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');
+ 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;
* then don't return anything. Otherwise, go through normal permission
* checks.
*/
- if (check_enable_rls(reloid, GetUserId(), true) == RLS_ENABLED)
+ if (check_enable_rls(reloid, InvalidOid, true) == RLS_ENABLED)
return NULL;
initStringInfo(&buf);
Relation rel;
Oid user_id;
- int sec_context;
int rls_status;
bool defaultDeny = false;
*hasRowSecurity = false;
*hasSubLinks = false;
- /* This is just to get the security context */
- GetUserIdAndSecContext(&user_id, &sec_context);
+ /* 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();
- /*
- * 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);
* privileges.
*/
- if (check_enable_rls(rel_oid, GetUserId(), true) != RLS_ENABLED)
+ if (check_enable_rls(rel_oid, InvalidOid, true) != RLS_ENABLED)
{
aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT);
if (aclresult != ACLCHECK_OK)
}
}
}
+ else
+ has_perm = false;
if (has_perm)
{
CachedPlanSource *plansource;
MemoryContext source_context;
MemoryContext oldcxt;
- Oid user_id;
- int security_context;
Assert(query_string != NULL); /* required as of 8.4 */
*/
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);
plansource->total_custom_cost = 0;
plansource->num_custom_plans = 0;
plansource->hasRowSecurity = false;
- plansource->rowSecurityDisabled
- = (security_context & SECURITY_ROW_LEVEL_DISABLED) != 0;
+ plansource->rowSecurityDisabled = InRowLevelSecurityDisabled();
plansource->row_security_env = row_security;
plansource->planUserId = InvalidOid;
* GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID
* and the SecurityRestrictionContext flags.
*
- * Currently there are two valid bits in SecurityRestrictionContext:
+ * 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
* 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
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
#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"
* 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).
+ * 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
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;
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
return RLS_NONE;
/* 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);
+}
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201507251
+#define CATALOG_VERSION_NO 201507281
#endif
#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
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);
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);
DELETE FROM category WHERE cid = 33; -- fails with FK violation
ERROR: update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
-DETAIL: Key (cid)=(33) is still referenced from table "document".
+DETAIL: Key is still referenced from table "document".
-- can insert FK referencing invisible PK
SET SESSION AUTHORIZATION rls_regress_user2;
SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
(1 row)
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 = force;
+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;
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));
+ 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,
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 = force;
+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;