summaryrefslogtreecommitdiff
path: root/src/backend/rewrite/rowsecurity.c
blob: f03b36a6e4e3a50f6928015fa9a4012b99a1646f (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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
/*
 * rewrite/rowsecurity.c
 *	  Routines to support policies for row-level security (aka RLS).
 *
 * Policies in PostgreSQL provide a mechanism to limit what records are
 * returned to a user and what records a user is permitted to add to a table.
 *
 * Policies can be defined for specific roles, specific commands, or provided
 * by an extension.  Row security can also be enabled for a table without any
 * policies being explicitly defined, in which case a default-deny policy is
 * applied.
 *
 * Any part of the system which is returning records back to the user, or
 * which is accepting records from the user to add to a table, needs to
 * consider the policies associated with the table (if any).  For normal
 * queries, this is handled by calling get_row_security_policies() during
 * rewrite, for each RTE in the query.  This returns the expressions defined
 * by the table's policies as a list that is prepended to the securityQuals
 * list for the RTE.  For queries which modify the table, any WITH CHECK
 * clauses from the table's policies are also returned and prepended to the
 * list of WithCheckOptions for the Query to check each row that is being
 * added to the table.  Other parts of the system (eg: COPY) simply construct
 * a normal query and use that, if RLS is to be applied.
 *
 * The check to see if RLS should be enabled is provided through
 * check_enable_rls(), which returns an enum (defined in rowsecurity.h) to
 * indicate if RLS should be enabled (RLS_ENABLED), or bypassed (RLS_NONE or
 * RLS_NONE_ENV).  RLS_NONE_ENV indicates that RLS should be bypassed
 * in the current environment, but that may change if the row_security GUC or
 * the current role changes.
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/pg_class.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/pg_list.h"
#include "nodes/plannodes.h"
#include "parser/parsetree.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rowsecurity.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/rls.h"
#include "utils/syscache.h"

static void get_policies_for_relation(Relation relation,
									  CmdType cmd, Oid user_id,
									  List **permissive_policies,
									  List **restrictive_policies);

static void sort_policies_by_name(List *policies);

static int	row_security_policy_cmp(const ListCell *a, const ListCell *b);

static void add_security_quals(int rt_index,
							   List *permissive_policies,
							   List *restrictive_policies,
							   List **securityQuals,
							   bool *hasSubLinks);

static void add_with_check_options(Relation rel,
								   int rt_index,
								   WCOKind kind,
								   List *permissive_policies,
								   List *restrictive_policies,
								   List **withCheckOptions,
								   bool *hasSubLinks,
								   bool force_using);

static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id);

/*
 * hooks to allow extensions to add their own security policies
 *
 * row_security_policy_hook_permissive can be used to add policies which
 * are combined with the other permissive policies, using OR.
 *
 * row_security_policy_hook_restrictive can be used to add policies which
 * are enforced, regardless of other policies (they are combined using AND).
 */
row_security_policy_hook_type row_security_policy_hook_permissive = NULL;
row_security_policy_hook_type row_security_policy_hook_restrictive = NULL;

/*
 * Get any row security quals and WithCheckOption checks that should be
 * applied to the specified RTE.
 *
 * In addition, hasRowSecurity is set to true if row-level security is enabled
 * (even if this RTE doesn't have any row security quals), and hasSubLinks is
 * set to true if any of the quals returned contain sublinks.
 */
void
get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
						  List **securityQuals, List **withCheckOptions,
						  bool *hasRowSecurity, bool *hasSubLinks)
{
	Oid			user_id;
	int			rls_status;
	Relation	rel;
	CmdType		commandType;
	List	   *permissive_policies;
	List	   *restrictive_policies;
	RTEPermissionInfo *perminfo;

	/* Defaults for the return values */
	*securityQuals = NIL;
	*withCheckOptions = NIL;
	*hasRowSecurity = false;
	*hasSubLinks = false;

	Assert(rte->rtekind == RTE_RELATION);

	/* If this is not a normal relation, just return immediately */
	if (rte->relkind != RELKIND_RELATION &&
		rte->relkind != RELKIND_PARTITIONED_TABLE)
		return;

	perminfo = getRTEPermissionInfo(root->rteperminfos, rte);

	/* Switch to checkAsUser if it's set */
	user_id = OidIsValid(perminfo->checkAsUser) ?
		perminfo->checkAsUser : GetUserId();

	/* Determine the state of RLS for this, pass checkAsUser explicitly */
	rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false);

	/* If there is no RLS on this table at all, nothing to do */
	if (rls_status == RLS_NONE)
		return;

	/*
	 * RLS_NONE_ENV means we are not doing any RLS now, but that may change
	 * with changes to the environment, so we mark it as hasRowSecurity to
	 * force a re-plan when the environment changes.
	 */
	if (rls_status == RLS_NONE_ENV)
	{
		/*
		 * Indicate that this query may involve RLS and must therefore be
		 * replanned if the environment changes (GUCs, role), but we are not
		 * adding anything here.
		 */
		*hasRowSecurity = true;

		return;
	}

	/*
	 * RLS is enabled for this relation.
	 *
	 * Get the security policies that should be applied, based on the command
	 * type.  Note that if this isn't the target relation, we actually want
	 * the relation's SELECT policies, regardless of the query command type,
	 * for example in UPDATE t1 ... FROM t2 we need to apply t1's UPDATE
	 * policies and t2's SELECT policies.
	 */
	rel = table_open(rte->relid, NoLock);

	commandType = rt_index == root->resultRelation ?
		root->commandType : CMD_SELECT;

	/*
	 * In some cases, we need to apply USING policies (which control the
	 * visibility of records) associated with multiple command types (see
	 * specific cases below).
	 *
	 * When considering the order in which to apply these USING policies, we
	 * prefer to apply higher privileged policies, those which allow the user
	 * to lock records (UPDATE and DELETE), first, followed by policies which
	 * don't (SELECT).
	 *
	 * Note that the optimizer is free to push down and reorder quals which
	 * use leakproof functions.
	 *
	 * In all cases, if there are no policy clauses allowing access to rows in
	 * the table for the specific type of operation, then a single
	 * always-false clause (a default-deny policy) will be added (see
	 * add_security_quals).
	 */

	/*
	 * For a SELECT, if UPDATE privileges are required (eg: the user has
	 * specified FOR [KEY] UPDATE/SHARE), then add the UPDATE USING quals
	 * first.
	 *
	 * This way, we filter out any records from the SELECT FOR SHARE/UPDATE
	 * which the user does not have access to via the UPDATE USING policies,
	 * similar to how we require normal UPDATE rights for these queries.
	 */
	if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
	{
		List	   *update_permissive_policies;
		List	   *update_restrictive_policies;

		get_policies_for_relation(rel, CMD_UPDATE, user_id,
								  &update_permissive_policies,
								  &update_restrictive_policies);

		add_security_quals(rt_index,
						   update_permissive_policies,
						   update_restrictive_policies,
						   securityQuals,
						   hasSubLinks);
	}

	/*
	 * For SELECT, UPDATE and DELETE, add security quals to enforce the USING
	 * policies.  These security quals control access to existing table rows.
	 * Restrictive policies are combined together using AND, and permissive
	 * policies are combined together using OR.
	 */

	get_policies_for_relation(rel, commandType, user_id, &permissive_policies,
							  &restrictive_policies);

	if (commandType == CMD_SELECT ||
		commandType == CMD_UPDATE ||
		commandType == CMD_DELETE)
		add_security_quals(rt_index,
						   permissive_policies,
						   restrictive_policies,
						   securityQuals,
						   hasSubLinks);

	/*
	 * Similar to above, during an UPDATE, DELETE, or MERGE, if SELECT rights
	 * are also required (eg: when a RETURNING clause exists, or the user has
	 * provided a WHERE clause which involves columns from the relation), we
	 * collect up CMD_SELECT policies and add them via add_security_quals
	 * first.
	 *
	 * This way, we filter out any records which are not visible through an
	 * ALL or SELECT USING policy.
	 */
	if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
		 commandType == CMD_MERGE) &&
		perminfo->requiredPerms & ACL_SELECT)
	{
		List	   *select_permissive_policies;
		List	   *select_restrictive_policies;

		get_policies_for_relation(rel, CMD_SELECT, user_id,
								  &select_permissive_policies,
								  &select_restrictive_policies);

		add_security_quals(rt_index,
						   select_permissive_policies,
						   select_restrictive_policies,
						   securityQuals,
						   hasSubLinks);
	}

	/*
	 * For INSERT and UPDATE, add withCheckOptions to verify that any new
	 * records added are consistent with the security policies.  This will use
	 * each policy's WITH CHECK clause, or its USING clause if no explicit
	 * WITH CHECK clause is defined.
	 */
	if (commandType == CMD_INSERT || commandType == CMD_UPDATE)
	{
		/* This should be the target relation */
		Assert(rt_index == root->resultRelation);

		add_with_check_options(rel, rt_index,
							   commandType == CMD_INSERT ?
							   WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK,
							   permissive_policies,
							   restrictive_policies,
							   withCheckOptions,
							   hasSubLinks,
							   false);

		/*
		 * Get and add ALL/SELECT policies, if SELECT rights are required for
		 * this relation (eg: when RETURNING is used).  These are added as WCO
		 * policies rather than security quals to ensure that an error is
		 * raised if a policy is violated; otherwise, we might end up silently
		 * dropping rows to be added.
		 */
		if (perminfo->requiredPerms & ACL_SELECT)
		{
			List	   *select_permissive_policies = NIL;
			List	   *select_restrictive_policies = NIL;

			get_policies_for_relation(rel, CMD_SELECT, user_id,
									  &select_permissive_policies,
									  &select_restrictive_policies);
			add_with_check_options(rel, rt_index,
								   commandType == CMD_INSERT ?
								   WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK,
								   select_permissive_policies,
								   select_restrictive_policies,
								   withCheckOptions,
								   hasSubLinks,
								   true);
		}

		/*
		 * For INSERT ... ON CONFLICT DO UPDATE we need additional policy
		 * checks for the UPDATE which may be applied to the same RTE.
		 */
		if (commandType == CMD_INSERT &&
			root->onConflict && root->onConflict->action == ONCONFLICT_UPDATE)
		{
			List	   *conflict_permissive_policies;
			List	   *conflict_restrictive_policies;
			List	   *conflict_select_permissive_policies = NIL;
			List	   *conflict_select_restrictive_policies = NIL;

			/* Get the policies that apply to the auxiliary UPDATE */
			get_policies_for_relation(rel, CMD_UPDATE, user_id,
									  &conflict_permissive_policies,
									  &conflict_restrictive_policies);

			/*
			 * Enforce the USING clauses of the UPDATE policies using WCOs
			 * rather than security quals.  This ensures that an error is
			 * raised if the conflicting row cannot be updated due to RLS,
			 * rather than the change being silently dropped.
			 */
			add_with_check_options(rel, rt_index,
								   WCO_RLS_CONFLICT_CHECK,
								   conflict_permissive_policies,
								   conflict_restrictive_policies,
								   withCheckOptions,
								   hasSubLinks,
								   true);

			/*
			 * Get and add ALL/SELECT policies, as WCO_RLS_CONFLICT_CHECK WCOs
			 * to ensure they are considered when taking the UPDATE path of an
			 * INSERT .. ON CONFLICT DO UPDATE, if SELECT rights are required
			 * for this relation, also as WCO policies, again, to avoid
			 * silently dropping data.  See above.
			 */
			if (perminfo->requiredPerms & ACL_SELECT)
			{
				get_policies_for_relation(rel, CMD_SELECT, user_id,
										  &conflict_select_permissive_policies,
										  &conflict_select_restrictive_policies);
				add_with_check_options(rel, rt_index,
									   WCO_RLS_CONFLICT_CHECK,
									   conflict_select_permissive_policies,
									   conflict_select_restrictive_policies,
									   withCheckOptions,
									   hasSubLinks,
									   true);
			}

			/* Enforce the WITH CHECK clauses of the UPDATE policies */
			add_with_check_options(rel, rt_index,
								   WCO_RLS_UPDATE_CHECK,
								   conflict_permissive_policies,
								   conflict_restrictive_policies,
								   withCheckOptions,
								   hasSubLinks,
								   false);

			/*
			 * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure
			 * that the final updated row is visible when taking the UPDATE
			 * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
			 * are required for this relation.
			 */
			if (perminfo->requiredPerms & ACL_SELECT)
				add_with_check_options(rel, rt_index,
									   WCO_RLS_UPDATE_CHECK,
									   conflict_select_permissive_policies,
									   conflict_select_restrictive_policies,
									   withCheckOptions,
									   hasSubLinks,
									   true);
		}
	}

	/*
	 * FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL)
	 * and set them up so that we can enforce the appropriate policy depending
	 * on the final action we take.
	 *
	 * We already fetched the SELECT policies above.
	 *
	 * We don't push the UPDATE/DELETE USING quals to the RTE because we don't
	 * really want to apply them while scanning the relation since we don't
	 * know whether we will be doing an UPDATE or a DELETE at the end. We
	 * apply the respective policy once we decide the final action on the
	 * target tuple.
	 *
	 * XXX We are setting up USING quals as WITH CHECK. If RLS prohibits
	 * UPDATE/DELETE on the target row, we shall throw an error instead of
	 * silently ignoring the row. This is different than how normal
	 * UPDATE/DELETE works and more in line with INSERT ON CONFLICT DO UPDATE
	 * handling.
	 */
	if (commandType == CMD_MERGE)
	{
		List	   *merge_permissive_policies;
		List	   *merge_restrictive_policies;

		/*
		 * Fetch the UPDATE policies and set them up to execute on the
		 * existing target row before doing UPDATE.
		 */
		get_policies_for_relation(rel, CMD_UPDATE, user_id,
								  &merge_permissive_policies,
								  &merge_restrictive_policies);

		/*
		 * WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on
		 * the existing target row.
		 */
		add_with_check_options(rel, rt_index,
							   WCO_RLS_MERGE_UPDATE_CHECK,
							   merge_permissive_policies,
							   merge_restrictive_policies,
							   withCheckOptions,
							   hasSubLinks,
							   true);

		/*
		 * Same with DELETE policies.
		 */
		get_policies_for_relation(rel, CMD_DELETE, user_id,
								  &merge_permissive_policies,
								  &merge_restrictive_policies);

		add_with_check_options(rel, rt_index,
							   WCO_RLS_MERGE_DELETE_CHECK,
							   merge_permissive_policies,
							   merge_restrictive_policies,
							   withCheckOptions,
							   hasSubLinks,
							   true);

		/*
		 * No special handling is required for INSERT policies. They will be
		 * checked and enforced during ExecInsert(). But we must add them to
		 * withCheckOptions.
		 */
		get_policies_for_relation(rel, CMD_INSERT, user_id,
								  &merge_permissive_policies,
								  &merge_restrictive_policies);

		add_with_check_options(rel, rt_index,
							   WCO_RLS_INSERT_CHECK,
							   merge_permissive_policies,
							   merge_restrictive_policies,
							   withCheckOptions,
							   hasSubLinks,
							   false);

		/* Enforce the WITH CHECK clauses of the UPDATE policies */
		add_with_check_options(rel, rt_index,
							   WCO_RLS_UPDATE_CHECK,
							   merge_permissive_policies,
							   merge_restrictive_policies,
							   withCheckOptions,
							   hasSubLinks,
							   false);
	}

	table_close(rel, NoLock);

	/*
	 * Copy checkAsUser to the row security quals and WithCheckOption checks,
	 * in case they contain any subqueries referring to other relations.
	 */
	setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
	setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);

	/*
	 * Mark this query as having row security, so plancache can invalidate it
	 * when necessary (eg: role changes)
	 */
	*hasRowSecurity = true;
}

/*
 * get_policies_for_relation
 *
 * Returns lists of permissive and restrictive policies to be applied to the
 * specified relation, based on the command type and role.
 *
 * This includes any policies added by extensions.
 */
static void
get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id,
						  List **permissive_policies,
						  List **restrictive_policies)
{
	ListCell   *item;

	*permissive_policies = NIL;
	*restrictive_policies = NIL;

	/* First find all internal policies for the relation. */
	foreach(item, relation->rd_rsdesc->policies)
	{
		bool		cmd_matches = false;
		RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);

		/* Always add ALL policies, if they exist. */
		if (policy->polcmd == '*')
			cmd_matches = true;
		else
		{
			/* Check whether the policy applies to the specified command type */
			switch (cmd)
			{
				case CMD_SELECT:
					if (policy->polcmd == ACL_SELECT_CHR)
						cmd_matches = true;
					break;
				case CMD_INSERT:
					if (policy->polcmd == ACL_INSERT_CHR)
						cmd_matches = true;
					break;
				case CMD_UPDATE:
					if (policy->polcmd == ACL_UPDATE_CHR)
						cmd_matches = true;
					break;
				case CMD_DELETE:
					if (policy->polcmd == ACL_DELETE_CHR)
						cmd_matches = true;
					break;
				case CMD_MERGE:

					/*
					 * We do not support a separate policy for MERGE command.
					 * Instead it derives from the policies defined for other
					 * commands.
					 */
					break;
				default:
					elog(ERROR, "unrecognized policy command type %d",
						 (int) cmd);
					break;
			}
		}

		/*
		 * Add this policy to the relevant list of policies if it applies to
		 * the specified role.
		 */
		if (cmd_matches && check_role_for_policy(policy->roles, user_id))
		{
			if (policy->permissive)
				*permissive_policies = lappend(*permissive_policies, policy);
			else
				*restrictive_policies = lappend(*restrictive_policies, policy);
		}
	}

	/*
	 * We sort restrictive policies by name so that any WCOs they generate are
	 * checked in a well-defined order.
	 */
	sort_policies_by_name(*restrictive_policies);

	/*
	 * Then add any permissive or restrictive policies defined by extensions.
	 * These are simply appended to the lists of internal policies, if they
	 * apply to the specified role.
	 */
	if (row_security_policy_hook_restrictive)
	{
		List	   *hook_policies =
		(*row_security_policy_hook_restrictive) (cmd, relation);

		/*
		 * As with built-in restrictive policies, we sort any hook-provided
		 * restrictive policies by name also.  Note that we also intentionally
		 * always check all built-in restrictive policies, in name order,
		 * before checking restrictive policies added by hooks, in name order.
		 */
		sort_policies_by_name(hook_policies);

		foreach(item, hook_policies)
		{
			RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);

			if (check_role_for_policy(policy->roles, user_id))
				*restrictive_policies = lappend(*restrictive_policies, policy);
		}
	}

	if (row_security_policy_hook_permissive)
	{
		List	   *hook_policies =
		(*row_security_policy_hook_permissive) (cmd, relation);

		foreach(item, hook_policies)
		{
			RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);

			if (check_role_for_policy(policy->roles, user_id))
				*permissive_policies = lappend(*permissive_policies, policy);
		}
	}
}

/*
 * sort_policies_by_name
 *
 * This is only used for restrictive policies, ensuring that any
 * WithCheckOptions they generate are applied in a well-defined order.
 * This is not necessary for permissive policies, since they are all combined
 * together using OR into a single WithCheckOption check.
 */
static void
sort_policies_by_name(List *policies)
{
	list_sort(policies, row_security_policy_cmp);
}

/*
 * list_sort comparator to sort RowSecurityPolicy entries by name
 */
static int
row_security_policy_cmp(const ListCell *a, const ListCell *b)
{
	const RowSecurityPolicy *pa = (const RowSecurityPolicy *) lfirst(a);
	const RowSecurityPolicy *pb = (const RowSecurityPolicy *) lfirst(b);

	/* Guard against NULL policy names from extensions */
	if (pa->policy_name == NULL)
		return pb->policy_name == NULL ? 0 : 1;
	if (pb->policy_name == NULL)
		return -1;

	return strcmp(pa->policy_name, pb->policy_name);
}

/*
 * add_security_quals
 *
 * Add security quals to enforce the specified RLS policies, restricting
 * access to existing data in a table.  If there are no policies controlling
 * access to the table, then all access is prohibited --- i.e., an implicit
 * default-deny policy is used.
 *
 * New security quals are added to securityQuals, and hasSubLinks is set to
 * true if any of the quals added contain sublink subqueries.
 */
static void
add_security_quals(int rt_index,
				   List *permissive_policies,
				   List *restrictive_policies,
				   List **securityQuals,
				   bool *hasSubLinks)
{
	ListCell   *item;
	List	   *permissive_quals = NIL;
	Expr	   *rowsec_expr;

	/*
	 * First collect up the permissive quals.  If we do not find any
	 * permissive policies then no rows are visible (this is handled below).
	 */
	foreach(item, permissive_policies)
	{
		RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);

		if (policy->qual != NULL)
		{
			permissive_quals = lappend(permissive_quals,
									   copyObject(policy->qual));
			*hasSubLinks |= policy->hassublinks;
		}
	}

	/*
	 * We must have permissive quals, always, or no rows are visible.
	 *
	 * If we do not, then we simply return a single 'false' qual which results
	 * in no rows being visible.
	 */
	if (permissive_quals != NIL)
	{
		/*
		 * We now know that permissive policies exist, so we can now add
		 * security quals based on the USING clauses from the restrictive
		 * policies.  Since these need to be combined together using AND, we
		 * can just add them one at a time.
		 */
		foreach(item, restrictive_policies)
		{
			RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
			Expr	   *qual;

			if (policy->qual != NULL)
			{
				qual = copyObject(policy->qual);
				ChangeVarNodes((Node *) qual, 1, rt_index, 0);

				*securityQuals = list_append_unique(*securityQuals, qual);
				*hasSubLinks |= policy->hassublinks;
			}
		}

		/*
		 * Then add a single security qual combining together the USING
		 * clauses from all the permissive policies using OR.
		 */
		if (list_length(permissive_quals) == 1)
			rowsec_expr = (Expr *) linitial(permissive_quals);
		else
			rowsec_expr = makeBoolExpr(OR_EXPR, permissive_quals, -1);

		ChangeVarNodes((Node *) rowsec_expr, 1, rt_index, 0);
		*securityQuals = list_append_unique(*securityQuals, rowsec_expr);
	}
	else

		/*
		 * A permissive policy must exist for rows to be visible at all.
		 * Therefore, if there were no permissive policies found, return a
		 * single always-false clause.
		 */
		*securityQuals = lappend(*securityQuals,
								 makeConst(BOOLOID, -1, InvalidOid,
										   sizeof(bool), BoolGetDatum(false),
										   false, true));
}

/*
 * add_with_check_options
 *
 * Add WithCheckOptions of the specified kind to check that new records
 * added by an INSERT or UPDATE are consistent with the specified RLS
 * policies.  Normally new data must satisfy the WITH CHECK clauses from the
 * policies.  If a policy has no explicit WITH CHECK clause, its USING clause
 * is used instead.  In the special case of an UPDATE arising from an
 * INSERT ... ON CONFLICT DO UPDATE, existing records are first checked using
 * a WCO_RLS_CONFLICT_CHECK WithCheckOption, which always uses the USING
 * clauses from RLS policies.
 *
 * New WCOs are added to withCheckOptions, and hasSubLinks is set to true if
 * any of the check clauses added contain sublink subqueries.
 */
static void
add_with_check_options(Relation rel,
					   int rt_index,
					   WCOKind kind,
					   List *permissive_policies,
					   List *restrictive_policies,
					   List **withCheckOptions,
					   bool *hasSubLinks,
					   bool force_using)
{
	ListCell   *item;
	List	   *permissive_quals = NIL;

#define QUAL_FOR_WCO(policy) \
	( !force_using && \
	  (policy)->with_check_qual != NULL ? \
	  (policy)->with_check_qual : (policy)->qual )

	/*
	 * First collect up the permissive policy clauses, similar to
	 * add_security_quals.
	 */
	foreach(item, permissive_policies)
	{
		RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
		Expr	   *qual = QUAL_FOR_WCO(policy);

		if (qual != NULL)
		{
			permissive_quals = lappend(permissive_quals, copyObject(qual));
			*hasSubLinks |= policy->hassublinks;
		}
	}

	/*
	 * There must be at least one permissive qual found or no rows are allowed
	 * to be added.  This is the same as in add_security_quals.
	 *
	 * If there are no permissive_quals then we fall through and return a
	 * single 'false' WCO, preventing all new rows.
	 */
	if (permissive_quals != NIL)
	{
		/*
		 * Add a single WithCheckOption for all the permissive policy clauses,
		 * combining them together using OR.  This check has no policy name,
		 * since if the check fails it means that no policy granted permission
		 * to perform the update, rather than any particular policy being
		 * violated.
		 */
		WithCheckOption *wco;

		wco = makeNode(WithCheckOption);
		wco->kind = kind;
		wco->relname = pstrdup(RelationGetRelationName(rel));
		wco->polname = NULL;
		wco->cascaded = false;

		if (list_length(permissive_quals) == 1)
			wco->qual = (Node *) linitial(permissive_quals);
		else
			wco->qual = (Node *) makeBoolExpr(OR_EXPR, permissive_quals, -1);

		ChangeVarNodes(wco->qual, 1, rt_index, 0);

		*withCheckOptions = list_append_unique(*withCheckOptions, wco);

		/*
		 * Now add WithCheckOptions for each of the restrictive policy clauses
		 * (which will be combined together using AND).  We use a separate
		 * WithCheckOption for each restrictive policy to allow the policy
		 * name to be included in error reports if the policy is violated.
		 */
		foreach(item, restrictive_policies)
		{
			RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
			Expr	   *qual = QUAL_FOR_WCO(policy);

			if (qual != NULL)
			{
				qual = copyObject(qual);
				ChangeVarNodes((Node *) qual, 1, rt_index, 0);

				wco = makeNode(WithCheckOption);
				wco->kind = kind;
				wco->relname = pstrdup(RelationGetRelationName(rel));
				wco->polname = pstrdup(policy->policy_name);
				wco->qual = (Node *) qual;
				wco->cascaded = false;

				*withCheckOptions = list_append_unique(*withCheckOptions, wco);
				*hasSubLinks |= policy->hassublinks;
			}
		}
	}
	else
	{
		/*
		 * If there were no policy clauses to check new data, add a single
		 * always-false WCO (a default-deny policy).
		 */
		WithCheckOption *wco;

		wco = makeNode(WithCheckOption);
		wco->kind = kind;
		wco->relname = pstrdup(RelationGetRelationName(rel));
		wco->polname = NULL;
		wco->qual = (Node *) makeConst(BOOLOID, -1, InvalidOid,
									   sizeof(bool), BoolGetDatum(false),
									   false, true);
		wco->cascaded = false;

		*withCheckOptions = lappend(*withCheckOptions, wco);
	}
}

/*
 * check_role_for_policy -
 *	 determines if the policy should be applied for the current role
 */
static bool
check_role_for_policy(ArrayType *policy_roles, Oid user_id)
{
	int			i;
	Oid		   *roles = (Oid *) ARR_DATA_PTR(policy_roles);

	/* Quick fall-thru for policies applied to all roles */
	if (roles[0] == ACL_ID_PUBLIC)
		return true;

	for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++)
	{
		if (has_privs_of_role(user_id, roles[i]))
			return true;
	}

	return false;
}