summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/misc/rls.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/misc/rls.c')
-rw-r--r--src/backend/utils/misc/rls.c167
1 files changed, 167 insertions, 0 deletions
diff --git a/src/backend/utils/misc/rls.c b/src/backend/utils/misc/rls.c
new file mode 100644
index 0000000..13d2515
--- /dev/null
+++ b/src/backend/utils/misc/rls.c
@@ -0,0 +1,167 @@
+/*-------------------------------------------------------------------------
+ *
+ * rls.c
+ * RLS-related utility functions.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/rls.c
+ *
+ *-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "access/htup.h"
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_class.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rls.h"
+#include "utils/syscache.h"
+#include "utils/varlena.h"
+
+
+/*
+ * check_enable_rls
+ *
+ * Determine, based on the relation, row_security setting, and current role,
+ * if RLS is applicable to this query. RLS_NONE_ENV indicates that, while
+ * RLS is not to be added for this query, a change in the environment may change
+ * that. RLS_NONE means that RLS is not on the relation at all and therefore
+ * we don't need to worry about it. RLS_ENABLED means RLS should be implemented
+ * 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). Pass
+ * InvalidOid to check the current user.
+ *
+ * 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
+ * allowed to. This allows users to check if RLS is enabled without having to
+ * deal with the actual error case (eg: error cases which are trying to decide
+ * if the user should get data from the relation back as part of the error).
+ */
+int
+check_enable_rls(Oid relid, Oid checkAsUser, bool noError)
+{
+ Oid user_id = checkAsUser ? checkAsUser : GetUserId();
+ HeapTuple tuple;
+ Form_pg_class classform;
+ bool relrowsecurity;
+ bool relforcerowsecurity;
+ bool amowner;
+
+ /* Nothing to do for built-in relations */
+ if (relid < (Oid) FirstNormalObjectId)
+ return RLS_NONE;
+
+ /* Fetch relation's relrowsecurity and relforcerowsecurity flags */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ return RLS_NONE;
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+
+ relrowsecurity = classform->relrowsecurity;
+ relforcerowsecurity = classform->relforcerowsecurity;
+
+ ReleaseSysCache(tuple);
+
+ /* Nothing to do if the relation does not have RLS */
+ if (!relrowsecurity)
+ return RLS_NONE;
+
+ /*
+ * BYPASSRLS users always bypass RLS. Note that superusers are always
+ * considered to have BYPASSRLS.
+ *
+ * Return RLS_NONE_ENV to indicate that this decision depends on the
+ * environment (in this case, the user_id).
+ */
+ if (has_bypassrls_privilege(user_id))
+ return RLS_NONE_ENV;
+
+ /*
+ * Table owners generally bypass RLS, except if the table has been set (by
+ * an owner) to FORCE ROW SECURITY, and this is not a referential
+ * integrity check.
+ *
+ * Return RLS_NONE_ENV to indicate that this decision depends on the
+ * environment (in this case, the user_id).
+ */
+ amowner = pg_class_ownercheck(relid, user_id);
+ if (amowner)
+ {
+ /*
+ * If FORCE ROW LEVEL SECURITY has been set on the relation then we
+ * should return RLS_ENABLED to indicate that RLS should be applied.
+ * If not, or if we are in an InNoForceRLSOperation context, we return
+ * RLS_NONE_ENV.
+ *
+ * InNoForceRLSOperation indicates that we should not apply RLS even
+ * if the table has FORCE RLS set - IF the current user is the owner.
+ * This is specifically to ensure that referential integrity checks
+ * are able to still run correctly.
+ *
+ * This is intentionally only done after we have checked that the user
+ * is the table owner, which should always be the case for referential
+ * integrity checks.
+ */
+ if (!relforcerowsecurity || InNoForceRLSOperation())
+ return RLS_NONE_ENV;
+ }
+
+ /*
+ * We should apply RLS. However, the user may turn off the row_security
+ * GUC to get a forced error instead.
+ */
+ if (!row_security && !noError)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("query would be affected by row-level security policy for table \"%s\"",
+ get_rel_name(relid)),
+ amowner ? errhint("To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.") : 0));
+
+ /* 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_PP(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);
+}