aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/misc/rls.c
blob: 6ce92af019948dfba7356654c724679af524bb21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*-------------------------------------------------------------------------
 *
 * rls.c
 *		  RLS-related utility functions.
 *
 * Portions Copyright (c) 1996-2015, 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/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, Oid checkAsUser, bool noError);

/*
 * 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)
{
	HeapTuple	tuple;
	Form_pg_class classform;
	bool		relrowsecurity;
	bool		relforcerowsecurity;
	Oid			user_id = checkAsUser ? checkAsUser : GetUserId();

	/* Nothing to do for built-in relations */
	if (relid < FirstNormalObjectId)
		return RLS_NONE;

	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 row_security=true and 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).
	 */
	if (pg_class_ownercheck(relid, user_id))
	{
		/*
		 * If row_security=true and FORCE ROW LEVEL SECURITY has been set on
		 * the relation then we return RLS_ENABLED to indicate that RLS should
		 * still be applied.  If we are in a SECURITY_NOFORCE_RLS context or if
		 * row_security=false then we return RLS_NONE_ENV.
		 *
		 * The SECURITY_NOFORCE_RLS 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 (row_security && relforcerowsecurity && !InNoForceRLSOperation())
			return RLS_ENABLED;
		else
			return RLS_NONE_ENV;
	}

	/* row_security GUC says to bypass RLS, but user lacks permission */
	if (!row_security && !noError)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("insufficient privilege to bypass row security.")));

	/* 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);
}