From ccfaf9067dfd0fa53f0c4c1245a76d310080a45c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 29 Apr 2002 22:15:07 +0000
Subject: [PATCH] Implement checking of USAGE rights on namespaces.

---
 src/backend/catalog/namespace.c | 304 +++++++++++++++++++++-----------
 1 file changed, 197 insertions(+), 107 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 902af689693..51ef7c3f8d9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.14 2002/04/27 03:45:00 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.15 2002/04/29 22:15:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,9 +37,10 @@
 #include "storage/backendid.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/catcache.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
-#include "utils/catcache.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
@@ -66,10 +67,25 @@
  * path is determined by GUC.  The factory default path contains the PUBLIC
  * namespace (if it exists), preceded by the user's personal namespace
  * (if one exists).
+ *
+ * If namespaceSearchPathValid is false, then namespaceSearchPath (and the
+ * derived variables) need to be recomputed from namespace_search_path.
+ * We mark it invalid upon an assignment to namespace_search_path or receipt
+ * of a syscache invalidation event for pg_namespace.  The recomputation
+ * is done during the next lookup attempt.
+ *
+ * Any namespaces mentioned in namespace_search_path that are not readable
+ * by the current user ID are simply left out of namespaceSearchPath; so
+ * we have to be willing to recompute the path when current userid changes.
+ * namespaceUser is the userid the path has been computed for.
  */
 
 static List *namespaceSearchPath = NIL;
 
+static bool namespaceSearchPathValid = true;
+
+static Oid	namespaceUser = InvalidOid;
+
 /* this flag must be updated correctly when namespaceSearchPath is changed */
 static bool pathContainsSystemNamespace = false;
 
@@ -103,12 +119,14 @@ typedef struct DelConstraint
 
 
 /* Local functions */
+static void recomputeNamespacePath(void);
 static Oid	GetTempTableNamespace(void);
 static void RemoveTempRelations(Oid tempNamespaceId);
 static List *FindTempRelations(Oid tempNamespaceId);
 static List *FindDeletionConstraints(List *relOids);
 static List *TopoSortRels(List *relOids, List *constraintList);
 static void RemoveTempRelationsCallback(void);
+static void NamespaceCallback(Datum arg, Oid relid);
 
 
 /*
@@ -137,12 +155,18 @@ RangeVarGetRelid(const RangeVar *relation, bool failOK)
 	if (relation->schemaname)
 	{
 		/* use exact schema given */
+		AclResult	aclresult;
+
 		namespaceId = GetSysCacheOid(NAMESPACENAME,
 									 CStringGetDatum(relation->schemaname),
 									 0, 0, 0);
 		if (!OidIsValid(namespaceId))
 			elog(ERROR, "Namespace \"%s\" does not exist",
 				 relation->schemaname);
+		aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, relation->schemaname);
+
 		relId = get_relname_relid(relation->relname, namespaceId);
 	}
 	else
@@ -210,11 +234,14 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
 	else
 	{
 		/* use the default creation namespace */
+		recomputeNamespacePath();
 		namespaceId = defaultCreationNamespace;
 		if (!OidIsValid(namespaceId))
 			elog(ERROR, "No namespace has been selected to create in");
 	}
 
+	/* Note: callers will check for CREATE rights when appropriate */
+
 	return namespaceId;
 }
 
@@ -229,9 +256,11 @@ RelnameGetRelid(const char *relname)
 	Oid			relid;
 	List	   *lptr;
 
+	recomputeNamespacePath();
+
 	/*
 	 * If a TEMP-table namespace has been set up, it is implicitly first
-	 * in the search path.
+	 * in the search path.  We do not need to check USAGE permission.
 	 */
 	if (OidIsValid(myTempNamespace))
 	{
@@ -241,7 +270,8 @@ RelnameGetRelid(const char *relname)
 	}
 
 	/*
-	 * If system namespace is not in path, implicitly search it before path
+	 * If system namespace is not in path, implicitly search it before path.
+	 * We do not check USAGE permission.
 	 */
 	if (!pathContainsSystemNamespace)
 	{
@@ -281,6 +311,8 @@ TypenameGetTypid(const char *typname)
 	Oid			typid;
 	List	   *lptr;
 
+	recomputeNamespacePath();
+
 	/*
 	 * If system namespace is not in path, implicitly search it before path
 	 */
@@ -327,6 +359,8 @@ OpclassnameGetOpcid(Oid amid, const char *opcname)
 	Oid			opcid;
 	List	   *lptr;
 
+	recomputeNamespacePath();
+
 	/*
 	 * If system namespace is not in path, implicitly search it before path
 	 */
@@ -414,17 +448,23 @@ FuncnameGetCandidates(List *names, int nargs)
 	if (schemaname)
 	{
 		/* use exact schema given */
+		AclResult	aclresult;
+
 		namespaceId = GetSysCacheOid(NAMESPACENAME,
 									 CStringGetDatum(schemaname),
 									 0, 0, 0);
 		if (!OidIsValid(namespaceId))
 			elog(ERROR, "Namespace \"%s\" does not exist",
 				 schemaname);
+		aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, schemaname);
 	}
 	else
 	{
 		/* flag to indicate we need namespace search */
 		namespaceId = InvalidOid;
+		recomputeNamespacePath();
 	}
 
 	/* Search syscache by name and (optionally) nargs only */
@@ -598,17 +638,23 @@ OpernameGetCandidates(List *names, char oprkind)
 	if (schemaname)
 	{
 		/* use exact schema given */
+		AclResult	aclresult;
+
 		namespaceId = GetSysCacheOid(NAMESPACENAME,
 									 CStringGetDatum(schemaname),
 									 0, 0, 0);
 		if (!OidIsValid(namespaceId))
 			elog(ERROR, "Namespace \"%s\" does not exist",
 				 schemaname);
+		aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, schemaname);
 	}
 	else
 	{
 		/* flag to indicate we need namespace search */
 		namespaceId = InvalidOid;
+		recomputeNamespacePath();
 	}
 
 	/* Search syscache by name only */
@@ -736,6 +782,8 @@ OpclassGetCandidates(Oid amid)
 	CatCList   *catlist;
 	int			i;
 
+	recomputeNamespacePath();
+
 	/* Search syscache by AM OID only */
 	catlist = SearchSysCacheList(CLAAMNAMENSP, 1,
 								 ObjectIdGetDatum(amid),
@@ -891,11 +939,14 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
 	else
 	{
 		/* use the default creation namespace */
+		recomputeNamespacePath();
 		namespaceId = defaultCreationNamespace;
 		if (!OidIsValid(namespaceId))
 			elog(ERROR, "No namespace has been selected to create in");
 	}
 
+	/* Note: callers will check for CREATE rights when appropriate */
+
 	*objname_p = objname;
 	return namespaceId;
 }
@@ -965,6 +1016,118 @@ isTempNamespace(Oid namespaceId)
 	return false;
 }
 
+/*
+ * recomputeNamespacePath - recompute path derived variables if needed.
+ */
+static void
+recomputeNamespacePath(void)
+{
+	Oid			userId = GetUserId();
+	char	   *rawname;
+	List	   *namelist;
+	List	   *oidlist;
+	List	   *newpath;
+	List	   *l;
+	MemoryContext oldcxt;
+
+	/*
+	 * Do nothing if path is already valid.
+	 */
+	if (namespaceSearchPathValid && namespaceUser == userId)
+		return;
+
+	/* Need a modifiable copy of namespace_search_path string */
+	rawname = pstrdup(namespace_search_path);
+
+	/* Parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &namelist))
+	{
+		/* syntax error in name list */
+		/* this should not happen if GUC checked check_search_path */
+		elog(ERROR, "recomputeNamespacePath: invalid list syntax");
+	}
+
+	/*
+	 * Convert the list of names to a list of OIDs.  If any names are not
+	 * recognizable or we don't have read access, just leave them out of
+	 * the list.  (We can't raise an error, since the search_path setting
+	 * has already been accepted.)
+	 */
+	oidlist = NIL;
+	foreach(l, namelist)
+	{
+		char   *curname = (char *) lfirst(l);
+		Oid		namespaceId;
+
+		if (strcmp(curname, "$user") == 0)
+		{
+			/* $user --- substitute namespace matching user name, if any */
+			HeapTuple	tuple;
+
+			tuple = SearchSysCache(SHADOWSYSID,
+								   ObjectIdGetDatum(userId),
+								   0, 0, 0);
+			if (HeapTupleIsValid(tuple))
+			{
+				char   *uname;
+
+				uname = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
+				namespaceId = GetSysCacheOid(NAMESPACENAME,
+											 CStringGetDatum(uname),
+											 0, 0, 0);
+				ReleaseSysCache(tuple);
+				if (OidIsValid(namespaceId) &&
+					pg_namespace_aclcheck(namespaceId, userId,
+										  ACL_USAGE) == ACLCHECK_OK)
+					oidlist = lappendi(oidlist, namespaceId);
+			}
+		}
+		else
+		{
+			/* normal namespace reference */
+			namespaceId = GetSysCacheOid(NAMESPACENAME,
+										 CStringGetDatum(curname),
+										 0, 0, 0);
+			if (OidIsValid(namespaceId) &&
+				pg_namespace_aclcheck(namespaceId, userId,
+									  ACL_USAGE) == ACLCHECK_OK)
+				oidlist = lappendi(oidlist, namespaceId);
+		}
+	}
+
+	/*
+	 * Now that we've successfully built the new list of namespace OIDs,
+	 * save it in permanent storage.
+	 */
+	oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+	newpath = listCopy(oidlist);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Now safe to assign to state variable. */
+	freeList(namespaceSearchPath);
+	namespaceSearchPath = newpath;
+
+	/*
+	 * Update info derived from search path.
+	 */
+	pathContainsSystemNamespace = intMember(PG_CATALOG_NAMESPACE,
+											namespaceSearchPath);
+
+	if (namespaceSearchPath == NIL)
+		defaultCreationNamespace = InvalidOid;
+	else
+		defaultCreationNamespace = (Oid) lfirsti(namespaceSearchPath);
+
+	/* Mark the path valid. */
+	namespaceSearchPathValid = true;
+	namespaceUser = userId;
+
+	/* Clean up. */
+	pfree(rawname);
+	freeList(namelist);
+	freeList(oidlist);
+}
+
 /*
  * GetTempTableNamespace
  *		Initialize temp table namespace on first use in a particular backend
@@ -979,8 +1142,12 @@ GetTempTableNamespace(void)
 	 * First, do permission check to see if we are authorized to make
 	 * temp tables.  We use a nonstandard error message here since
 	 * "databasename: permission denied" might be a tad cryptic.
+	 *
+	 * Note we apply the check to the session user, not the currently
+	 * active userid, since we are not going to change our minds about
+	 * temp table availability during the session.
 	 */
-	if (pg_database_aclcheck(MyDatabaseId, GetUserId(),
+	if (pg_database_aclcheck(MyDatabaseId, GetSessionUserId(),
 							 ACL_CREATE_TEMP) != ACLCHECK_OK)
 		elog(ERROR, "%s: not authorized to create temp tables",
 			 DatabaseName);
@@ -1320,7 +1487,8 @@ check_search_path(const char *proposed)
 
 	/*
 	 * Verify that all the names are either valid namespace names or "$user".
-	 * (We do not require $user to correspond to a valid namespace; should we?)
+	 * We do not require $user to correspond to a valid namespace.
+	 * We do not check for USAGE rights, either; should we?
 	 */
 	foreach(l, namelist)
 	{
@@ -1348,104 +1516,12 @@ check_search_path(const char *proposed)
 void
 assign_search_path(const char *newval)
 {
-	char	   *rawname;
-	List	   *namelist;
-	List	   *oidlist;
-	List	   *newpath;
-	List	   *l;
-	MemoryContext oldcxt;
-
-	/*
-	 * If we aren't inside a transaction, we cannot do database access so
-	 * cannot look up the names.  In this case, do nothing; the internal
-	 * search path will be fixed later by InitializeSearchPath.  (We assume
-	 * this situation can only happen in the postmaster or early in backend
-	 * startup.)
-	 */
-	if (!IsTransactionState())
-		return;
-
-	/* Need a modifiable copy of string */
-	rawname = pstrdup(newval);
-
-	/* Parse string into list of identifiers */
-	if (!SplitIdentifierString(rawname, ',', &namelist))
-	{
-		/* syntax error in name list */
-		/* this should not happen if GUC checked check_search_path */
-		elog(ERROR, "assign_search_path: invalid list syntax");
-	}
-
 	/*
-	 * Convert the list of names to a list of OIDs.  If any names are not
-	 * recognizable, just leave them out of the list.  (This is our only
-	 * reasonable recourse when the already-accepted default is bogus.)
+	 * We mark the path as needing recomputation, but don't do anything until
+	 * it's needed.  This avoids trying to do database access during GUC
+	 * initialization.
 	 */
-	oidlist = NIL;
-	foreach(l, namelist)
-	{
-		char   *curname = (char *) lfirst(l);
-		Oid		namespaceId;
-
-		if (strcmp(curname, "$user") == 0)
-		{
-			/* $user --- substitute namespace matching user name, if any */
-			HeapTuple	tuple;
-
-			tuple = SearchSysCache(SHADOWSYSID,
-								   ObjectIdGetDatum(GetSessionUserId()),
-								   0, 0, 0);
-			if (HeapTupleIsValid(tuple))
-			{
-				char   *uname;
-
-				uname = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
-				namespaceId = GetSysCacheOid(NAMESPACENAME,
-											 CStringGetDatum(uname),
-											 0, 0, 0);
-				if (OidIsValid(namespaceId))
-					oidlist = lappendi(oidlist, namespaceId);
-				ReleaseSysCache(tuple);
-			}
-		}
-		else
-		{
-			/* normal namespace reference */
-			namespaceId = GetSysCacheOid(NAMESPACENAME,
-										 CStringGetDatum(curname),
-										 0, 0, 0);
-			if (OidIsValid(namespaceId))
-				oidlist = lappendi(oidlist, namespaceId);
-		}
-	}
-
-	/*
-	 * Now that we've successfully built the new list of namespace OIDs,
-	 * save it in permanent storage.
-	 */
-	oldcxt = MemoryContextSwitchTo(TopMemoryContext);
-	newpath = listCopy(oidlist);
-	MemoryContextSwitchTo(oldcxt);
-
-	/* Now safe to assign to state variable. */
-	freeList(namespaceSearchPath);
-	namespaceSearchPath = newpath;
-
-	/*
-	 * Update info derived from search path.
-	 */
-	pathContainsSystemNamespace = intMember(PG_CATALOG_NAMESPACE,
-											namespaceSearchPath);
-
-	if (namespaceSearchPath == NIL)
-		defaultCreationNamespace = InvalidOid;
-	else
-		defaultCreationNamespace = (Oid) lfirsti(namespaceSearchPath);
-
-	/* Clean up. */
-	pfree(rawname);
-	freeList(namelist);
-	freeList(oidlist);
+	namespaceSearchPathValid = false;
 }
 
 /*
@@ -1469,19 +1545,32 @@ InitializeSearchPath(void)
 		MemoryContextSwitchTo(oldcxt);
 		pathContainsSystemNamespace = true;
 		defaultCreationNamespace = PG_CATALOG_NAMESPACE;
+		namespaceSearchPathValid = true;
+		namespaceUser = GetUserId();
 	}
 	else
 	{
 		/*
-		 * If a search path setting was provided before we were able to
-		 * execute lookups, establish the internal search path now.
+		 * In normal mode, arrange for a callback on any syscache invalidation
+		 * of pg_namespace rows.
 		 */
-		if (namespace_search_path && *namespace_search_path &&
-			namespaceSearchPath == NIL)
-			assign_search_path(namespace_search_path);
+		CacheRegisterSyscacheCallback(NAMESPACEOID,
+									  NamespaceCallback,
+									  (Datum) 0);
 	}
 }
 
+/*
+ * NamespaceCallback
+ *		Syscache inval callback function
+ */
+static void
+NamespaceCallback(Datum arg, Oid relid)
+{
+	/* Force search path to be recomputed on next use */
+	namespaceSearchPathValid = false;
+}
+
 /*
  * Fetch the active search path, expressed as a List of OIDs.
  *
@@ -1490,5 +1579,6 @@ InitializeSearchPath(void)
 List *
 fetch_search_path(void)
 {
+	recomputeNamespacePath();
 	return namespaceSearchPath;
 }
-- 
GitLab