From b0f5797d81a4b856f26818e14c93a0ec453a35de Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Fri, 24 Jan 2014 16:18:40 +0800
Subject: [PATCH 2/4] RLS: Enforce row-security constraints

Row-security constraints are enforced here by having the securityQual expansion
code check for a row-security constraint before expanding quals.
---
 src/backend/optimizer/prep/prepsecurity.c |  17 +++-
 src/backend/optimizer/prep/prepunion.c    |   5 ++
 src/backend/optimizer/util/Makefile       |   3 +-
 src/backend/optimizer/util/rowsecurity.c  | 144 ++++++++++++++++++++++++++++++
 src/include/optimizer/rowsecurity.h       |  25 ++++++
 5 files changed, 192 insertions(+), 2 deletions(-)
 create mode 100644 src/backend/optimizer/util/rowsecurity.c
 create mode 100644 src/include/optimizer/rowsecurity.h

diff --git a/src/backend/optimizer/prep/prepsecurity.c b/src/backend/optimizer/prep/prepsecurity.c
index e7a3f56..ad682bb 100644
--- a/src/backend/optimizer/prep/prepsecurity.c
+++ b/src/backend/optimizer/prep/prepsecurity.c
@@ -1,7 +1,8 @@
 /*-------------------------------------------------------------------------
  *
  * prepsecurity.c
- *	  Routines for preprocessing security barrier quals.
+ *	  Routines for preprocessing security barrier quals and applying row-security
+ *	  policies.
  *
  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -15,6 +16,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/transam.h"
 #include "access/sysattr.h"
 #include "catalog/heap.h"
 #include "nodes/makefuncs.h"
@@ -23,6 +25,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/rowsecurity.h"
 
 
 typedef struct
@@ -75,6 +78,18 @@ expand_security_quals(PlannerInfo *root, List *tlist)
 		rt_index++;
 		rte = rt_fetch(rt_index, parse->rtable);
 
+		/*
+		 * Check for row-security quals on the relation and, if found, prepend them
+		 * as new inner-most security quals. Ignore the return, we don't care at the
+		 * moment.
+		 *
+		 * This will set rowsec_done on the RTE, which we'll copy if we expand
+		 * it, ensuring that no infinitely recursive expansion of row security quals
+		 * is done.
+		 */
+		(void) prepend_row_security_quals(root, rte);
+
+
 		if (rte->securityQuals == NIL)
 			continue;
 
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index c7e0199..0ae60c1 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1349,6 +1349,11 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		childRTindex = list_length(parse->rtable);
 
 		/*
+		 * XXX TODO We need to apply row-security policy after inheritance
+		 * expansion.
+		 */
+
+		/*
 		 * Build an AppendRelInfo for this parent and child.
 		 */
 		appinfo = makeNode(AppendRelInfo);
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index c54d0a6..d42a285 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \
-       plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o
+       plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o \
+       rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index 0000000..3f26d2f
--- /dev/null
+++ b/src/backend/optimizer/util/rowsecurity.c
@@ -0,0 +1,144 @@
+/*
+ * rewrite/rowsecurity.c
+ *    Routines to support row-security feature
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+/* hook to allow extensions to apply their own security policy */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+static void
+add_uid_plan_inval(PlannerGlobal* glob);
+
+static List *
+pull_row_security_policy(CmdType cmd, Relation relation);
+
+/*
+ * Check the given RTE to see whether it's already had row-security
+ * quals expanded and, if not, prepend any row-security rules
+ * from built-in or plug-in sources to the securityQuals.
+ *
+ * Returns true if any quals were added.
+ */
+bool
+prepend_row_security_quals(PlannerInfo* root, RangeTblEntry* rte)
+{
+	List	   *rowsecquals;
+	Relation 	rel;
+	Oid			userid;
+	int			sec_context;
+
+	GetUserIdAndSecContext(&userid, &sec_context);
+
+	bool qualsAdded = false;
+	if (rte->relid >= FirstNormalObjectId
+		&& rte->relkind == 'r'
+		&& !rte->rowsec_done
+		&& !(sec_context & SECURITY_ROW_LEVEL_DISABLED))
+	{
+		rel = heap_open(rte->relid, NoLock);
+		rowsecquals = pull_row_security_policy(root->parse->commandType, rel);
+		if (rowsecquals)
+		{
+			rte->securityQuals = list_concat(rowsecquals, rte->securityQuals);
+			qualsAdded = true;
+		}
+		heap_close(rel, NoLock);
+		rte->rowsec_done = true;
+	}
+	if (qualsAdded)
+		add_uid_plan_inval(root->glob);
+	return qualsAdded;
+}
+
+/*
+ * pull_row_security_policy
+ *
+ * Fetches the configured row-security policy of both built-in catalogs and any
+ * extensions. If any policy is found a list of qualifier expressions is
+ * returned, where each is treated as a securityQual.
+ */
+static List *
+pull_row_security_policy(CmdType cmd, Relation relation)
+{
+	List   *quals = NIL;
+	Expr   *qual = NULL;
+
+	/*
+	 * Pull the row-security policy configured with built-in features,
+	 * if unprivileged users. Please note that superuser can bypass it.
+	 */
+	if (relation->rsdesc && !superuser())
+	{
+		RowSecurityDesc *rsdesc = relation->rsdesc;
+		qual = copyObject(rsdesc->rsall.qual);
+		quals = lcons(qual, quals);
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-security policy. If both built-in and extension has
+	 * their own policy they're applied as nested qualifiers.
+	 */
+	if (row_security_policy_hook)
+	{
+		List   *temp;
+
+		temp = (*row_security_policy_hook)(cmd, relation);
+		if (temp != NIL)
+			lcons(temp, quals);
+	}
+	return quals;
+}
+
+/*
+ * Row-security plans are dependent on the current user id because of the if
+ * (superuser) test. So row-security plans must be invalidated if the user id
+ * changes.
+ */
+static void
+add_uid_plan_inval(PlannerGlobal* glob)
+{
+	PlanInvalItem  *pi_item;
+
+	if (!OidIsValid(glob->planUserId))
+	{
+		/* Plan invalidation on session user-id */
+		glob->planUserId = GetUserId();
+	
+		/* Plan invalidation on catalog updates of pg_authid */
+		pi_item = makeNode(PlanInvalItem);
+		pi_item->cacheId = AUTHOID;
+		pi_item->hashValue =
+		GetSysCacheHashValue1(AUTHOID,
+	    					  ObjectIdGetDatum(glob->planUserId));
+		glob->invalItems = lappend(glob->invalItems, pi_item);
+	}
+	else
+		Assert(glob->planUserId == GetUserId());
+
+}
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index 0000000..453d79a
--- /dev/null
+++ b/src/include/optimizer/rowsecurity.h
@@ -0,0 +1,25 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowsecurity.h
+ *    prototypes for optimizer/rowsecurity.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWSECURITY_H
+#define ROWSECURITY_H
+
+#include "nodes/execnodes.h"
+#include "nodes/parsenodes.h"
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*row_security_policy_hook_type)(CmdType cmdtype,
+											   Relation relation);
+extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook;
+
+extern bool prepend_row_security_quals(PlannerInfo* root, RangeTblEntry* rte);
+
+#endif	/* ROWSECURITY_H */
-- 
1.8.3.1