Skip to content

Commit baf6f54

Browse files
author
Commitfest Bot
committed
[CF 6178] v1 - COPY FROM with RLS
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/6178 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CACJufxFbmnoa5O-vL43DPTCGt6oagY4dXgKxy=rcD9-e9g0zEg@mail.gmail.com Author(s): Jian He
2 parents 81f7211 + 5d73e95 commit baf6f54

File tree

5 files changed

+238
-14
lines changed

5 files changed

+238
-14
lines changed

doc/src/sgml/ref/copy.sgml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,9 +569,11 @@ COPY <replaceable class="parameter">count</replaceable>
569569
If row-level security is enabled for the table, the relevant
570570
<command>SELECT</command> policies will apply to <literal>COPY
571571
<replaceable class="parameter">table</replaceable> TO</literal> statements.
572-
Currently, <command>COPY FROM</command> is not supported for tables
573-
with row-level security. Use equivalent <command>INSERT</command>
574-
statements instead.
572+
<command>COPY FROM</command> is supported for tables with row-level security.
573+
However if any row-level security policy’s <literal>USING</literal> or
574+
<literal>WITH CHECK</literal> expression contains a subquery, then
575+
<command>COPY FROM</command> is not supported. In that case, Use equivalent
576+
<command>INSERT</command> statements instead.
575577
</para>
576578

577579
<para>

src/backend/commands/copy.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -218,20 +218,14 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
218218
* If RLS is not enabled for this, then just fall through to the
219219
* normal non-filtering relation handling.
220220
*/
221-
if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
221+
if (!is_from && check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
222222
{
223223
SelectStmt *select;
224224
ColumnRef *cr;
225225
ResTarget *target;
226226
RangeVar *from;
227227
List *targetList = NIL;
228228

229-
if (is_from)
230-
ereport(ERROR,
231-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
232-
errmsg("COPY FROM not supported with row-level security"),
233-
errhint("Use INSERT statements instead.")));
234-
235229
/*
236230
* Build target list
237231
*

src/backend/commands/copyfrom.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,20 @@
3939
#include "foreign/fdwapi.h"
4040
#include "mb/pg_wchar.h"
4141
#include "miscadmin.h"
42+
#include "nodes/makefuncs.h"
4243
#include "nodes/miscnodes.h"
4344
#include "optimizer/optimizer.h"
45+
#include "parser/parse_relation.h"
4446
#include "pgstat.h"
4547
#include "rewrite/rewriteHandler.h"
48+
#include "rewrite/rowsecurity.h"
4649
#include "storage/fd.h"
4750
#include "tcop/tcopprot.h"
4851
#include "utils/lsyscache.h"
4952
#include "utils/memutils.h"
5053
#include "utils/portal.h"
5154
#include "utils/rel.h"
55+
#include "utils/rls.h"
5256
#include "utils/snapmgr.h"
5357

5458
/*
@@ -957,6 +961,109 @@ CopyFrom(CopyFromState cstate)
957961

958962
Assert(resultRelInfo->ri_BatchSize >= 1);
959963

964+
if (check_enable_rls(RelationGetRelid(cstate->rel), InvalidOid, false) == RLS_ENABLED)
965+
{
966+
List *securityQuals = NIL;
967+
List *withCheckOptions = NIL;
968+
List *newWithCheckOptions = NIL;
969+
List *wcoExprs = NIL;
970+
char *refname;
971+
972+
Query *root = makeNode(Query);
973+
ModifyTable *node = makeNode(ModifyTable);
974+
RangeTblEntry *rte = makeNode(RangeTblEntry);
975+
bool hasRowSecurity = false;
976+
bool hasSubLinks = false;
977+
RTEPermissionInfo *perminfo;
978+
979+
/*
980+
* We use the Query and RTE nodes to retrieve the COPY FROM relation's
981+
* security policies (get_row_security_policies) and transform them into
982+
* WithCheckOption nodes. Later, we initialize these WCO node exprstate
983+
* and pass these initialized WCOs to the resultRelInfo.
984+
*/
985+
rte->alias = NULL;
986+
refname = RelationGetRelationName(cstate->rel);
987+
rte->eref = makeAlias(refname, NIL);
988+
rte->rtekind = RTE_RELATION;
989+
rte->relid = RelationGetRelid(cstate->rel);
990+
rte->inh = false;
991+
rte->relkind = cstate->rel->rd_rel->relkind;
992+
rte->rellockmode = RowExclusiveLock;
993+
rte->lateral = false;
994+
rte->inFromCl = false;
995+
996+
perminfo = addRTEPermissionInfo(&root->rteperminfos, rte);
997+
perminfo->requiredPerms = ACL_INSERT;
998+
perminfo->checkAsUser = InvalidOid;
999+
1000+
/* we already did the permission check on DoCopy */
1001+
foreach_int(cur, cstate->attnumlist)
1002+
{
1003+
int attno;
1004+
Bitmapset **bms;
1005+
1006+
attno = cur - FirstLowInvalidHeapAttributeNumber;
1007+
bms = &perminfo->insertedCols;
1008+
1009+
*bms = bms_add_member(*bms, attno);
1010+
}
1011+
1012+
root->resultRelation = 1;
1013+
root->rtable = list_make1(rte);
1014+
root->commandType = CMD_INSERT;
1015+
root->stmt_location = -1;
1016+
1017+
get_row_security_policies(root, rte, 1,
1018+
&securityQuals,
1019+
&withCheckOptions,
1020+
&hasRowSecurity,
1021+
&hasSubLinks);
1022+
1023+
/* policy contain subquery, maybe doable? */
1024+
if (hasSubLinks)
1025+
ereport(ERROR,
1026+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1027+
errmsg("COPY FROM not supported with row-level security policy contain subquery"),
1028+
errhint("Use INSERT statements instead."));
1029+
1030+
foreach_node(WithCheckOption, wco, withCheckOptions)
1031+
{
1032+
wco->qual = eval_const_expressions(NULL, wco->qual);
1033+
wco->qual = (Node *) canonicalize_qual((Expr *) wco->qual, false);
1034+
wco->qual = (Node *) make_ands_implicit((Expr *) wco->qual);
1035+
1036+
if (wco->qual != NULL)
1037+
newWithCheckOptions = lappend(newWithCheckOptions, wco);
1038+
}
1039+
1040+
foreach_node(WithCheckOption, wco, newWithCheckOptions)
1041+
{
1042+
ExprState *wcoExpr = ExecInitQual(castNode(List, wco->qual),
1043+
&mtstate->ps);
1044+
1045+
wcoExprs = lappend(wcoExprs, wcoExpr);
1046+
}
1047+
1048+
resultRelInfo->ri_WithCheckOptions = newWithCheckOptions;
1049+
resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
1050+
1051+
/* see make_modifytable */
1052+
node->operation = CMD_INSERT;
1053+
1054+
/*
1055+
* INSERT applies to a single relation only, so rootRelation is always 0
1056+
*/
1057+
node->rootRelation = 0;
1058+
node->returningOldAlias = NULL;
1059+
node->returningNewAlias = NULL;
1060+
node->resultRelations = list_make1_int(1);
1061+
node->onConflictAction = ONCONFLICT_NONE;
1062+
node->withCheckOptionLists = list_make1(list_copy(newWithCheckOptions));
1063+
1064+
mtstate->ps.plan = (Plan *) node;
1065+
}
1066+
9601067
/* Prepare to catch AFTER triggers. */
9611068
AfterTriggerBeginQuery();
9621069

@@ -1349,6 +1456,13 @@ CopyFrom(CopyFromState cstate)
13491456
ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
13501457
CMD_INSERT);
13511458

1459+
/* do row level security policy check */
1460+
if (resultRelInfo->ri_WithCheckOptions != NIL)
1461+
ExecWithCheckOptions(WCO_RLS_INSERT_CHECK,
1462+
resultRelInfo,
1463+
myslot,
1464+
estate);
1465+
13521466
/*
13531467
* If the target is a plain table, check the constraints of
13541468
* the tuple.

src/test/regress/expected/rowsecurity.out

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dt
615615
-- back from p1r for this because it sorts first
616616
INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
617617
ERROR: new row violates row-level security policy "p1r" for table "document"
618+
-- fail, COPY FROM, security policy with subquery not supported
619+
COPY document FROM STDIN;
620+
ERROR: COPY FROM not supported with row-level security policy contain subquery
621+
HINT: Use INSERT statements instead.
618622
-- Just to see a p2r error
619623
INSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
620624
ERROR: new row violates row-level security policy "p2r" for table "document"
@@ -1351,6 +1355,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
13511355
-- pp1 ERROR
13521356
INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
13531357
ERROR: new row violates row-level security policy for table "part_document"
1358+
-- fail, COPY FROM, security policy with subquery not supported
1359+
COPY part_document FROM STDIN;
1360+
ERROR: COPY FROM not supported with row-level security policy contain subquery
1361+
HINT: Use INSERT statements instead.
13541362
-- pp1r ERROR
13551363
INSERT INTO part_document VALUES (100, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail
13561364
ERROR: new row violates row-level security policy "pp1r" for table "part_document"
@@ -1635,6 +1643,49 @@ EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualifie
16351643
Seq Scan on dependent
16361644
(1 row)
16371645

1646+
--COPY FROM with RLS
1647+
RESET SESSION AUTHORIZATION;
1648+
CREATE TABLE pp (id int,val int) PARTITION BY RANGE (id);
1649+
CREATE TABLE pp_1 (val int, id int);
1650+
ALTER TABLE pp ATTACH PARTITION pp_1 FOR VALUES FROM (1) TO (10);
1651+
CREATE TABLE pp_2 PARTITION OF pp FOR VALUES FROM (10) TO (20);
1652+
ALTER TABLE pp ENABLE ROW LEVEL SECURITY;
1653+
ALTER TABLE pp_1 ENABLE ROW LEVEL SECURITY;
1654+
ALTER TABLE pp_2 ENABLE ROW LEVEL SECURITY;
1655+
CREATE POLICY pp_1_p1 ON pp_1 FOR INSERT WITH CHECK (id = 6);
1656+
CREATE POLICY p2_pp ON pp FOR ALL USING(id = 1 or id = 2);
1657+
GRANT SELECT, INSERT ON pp TO regress_rls_alice;
1658+
GRANT SELECT, INSERT ON pp_1 TO regress_rls_alice;
1659+
GRANT SELECT, INSERT ON pp_2 TO regress_rls_alice;
1660+
SET SESSION AUTHORIZATION regress_rls_alice;
1661+
INSERT INTO pp_1 VALUES (13, 2); --error
1662+
ERROR: new row violates row-level security policy for table "pp_1"
1663+
INSERT INTO pp_1 VALUES (16, 6); --ok
1664+
COPY pp_1 FROM STDIN WITH DELIMITER ','; --second record not ok
1665+
ERROR: new row violates row-level security policy for table "pp_1"
1666+
CONTEXT: COPY pp_1, line 2: "13,2"
1667+
INSERT INTO pp VALUES (1,11), (2,12);
1668+
INSERT INTO pp values (5,11); --error
1669+
ERROR: new row violates row-level security policy for table "pp"
1670+
INSERT INTO pp values (6,11); --error
1671+
ERROR: new row violates row-level security policy for table "pp"
1672+
COPY pp FROM STDIN WITH DELIMITER ','; --error, second record not ok
1673+
ERROR: new row violates row-level security policy for table "pp"
1674+
CONTEXT: COPY pp, line 2: "5,11"
1675+
COPY pp FROM STDIN WITH DELIMITER ','; --error, second record not ok
1676+
ERROR: new row violates row-level security policy for table "pp"
1677+
CONTEXT: COPY pp, line 2: "6,11"
1678+
RESET SESSION AUTHORIZATION;
1679+
CREATE POLICY p1_pp ON pp FOR INSERT WITH CHECK(id > 4);
1680+
SET SESSION AUTHORIZATION regress_rls_alice;
1681+
INSERT INTO pp VALUES (5, 15), (6, 16); --ok
1682+
INSERT INTO pp VALUES (4, 14); --error
1683+
ERROR: new row violates row-level security policy for table "pp"
1684+
COPY pp FROM STDIN WITH DELIMITER ','; --third record will result error
1685+
ERROR: new row violates row-level security policy for table "pp"
1686+
CONTEXT: COPY pp, line 3: "4,14"
1687+
RESET SESSION AUTHORIZATION;
1688+
DROP TABLE PP;
16381689
----- RECURSION ----
16391690
--
16401691
-- Simple recursion
@@ -4079,9 +4130,7 @@ SET row_security TO OFF;
40794130
COPY copy_t FROM STDIN; --fail - would be affected by RLS.
40804131
ERROR: query would be affected by row-level security policy for table "copy_t"
40814132
SET row_security TO ON;
4082-
COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
4083-
ERROR: COPY FROM not supported with row-level security
4084-
HINT: Use INSERT statements instead.
4133+
COPY copy_t FROM STDIN; --no error
40854134
-- Check COPY FROM as user with permissions and BYPASSRLS
40864135
SET SESSION AUTHORIZATION regress_rls_exempt_user;
40874136
SET row_security TO ON;
@@ -4597,6 +4646,7 @@ ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
45974646
ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
45984647
-- Works fine
45994648
INSERT INTO r1 VALUES (10), (20);
4649+
COPY r1 FROM STDIN;
46004650
-- No error, but no rows
46014651
TABLE r1;
46024652
a

src/test/regress/sql/rowsecurity.sql

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dt
283283
-- 44 would technically fail for both p2r and p1r, but we should get an error
284284
-- back from p1r for this because it sorts first
285285
INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
286+
287+
-- fail, COPY FROM, security policy with subquery not supported
288+
COPY document FROM STDIN;
289+
\.
290+
286291
-- Just to see a p2r error
287292
INSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
288293

@@ -518,6 +523,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
518523

519524
-- pp1 ERROR
520525
INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
526+
-- fail, COPY FROM, security policy with subquery not supported
527+
COPY part_document FROM STDIN;
528+
\.
529+
521530
-- pp1r ERROR
522531
INSERT INTO part_document VALUES (100, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail
523532

@@ -618,6 +627,56 @@ DROP TABLE dependee CASCADE;
618627

619628
EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified
620629

630+
--COPY FROM with RLS
631+
RESET SESSION AUTHORIZATION;
632+
CREATE TABLE pp (id int,val int) PARTITION BY RANGE (id);
633+
CREATE TABLE pp_1 (val int, id int);
634+
ALTER TABLE pp ATTACH PARTITION pp_1 FOR VALUES FROM (1) TO (10);
635+
CREATE TABLE pp_2 PARTITION OF pp FOR VALUES FROM (10) TO (20);
636+
ALTER TABLE pp ENABLE ROW LEVEL SECURITY;
637+
ALTER TABLE pp_1 ENABLE ROW LEVEL SECURITY;
638+
ALTER TABLE pp_2 ENABLE ROW LEVEL SECURITY;
639+
CREATE POLICY pp_1_p1 ON pp_1 FOR INSERT WITH CHECK (id = 6);
640+
CREATE POLICY p2_pp ON pp FOR ALL USING(id = 1 or id = 2);
641+
GRANT SELECT, INSERT ON pp TO regress_rls_alice;
642+
GRANT SELECT, INSERT ON pp_1 TO regress_rls_alice;
643+
GRANT SELECT, INSERT ON pp_2 TO regress_rls_alice;
644+
645+
SET SESSION AUTHORIZATION regress_rls_alice;
646+
INSERT INTO pp_1 VALUES (13, 2); --error
647+
INSERT INTO pp_1 VALUES (16, 6); --ok
648+
COPY pp_1 FROM STDIN WITH DELIMITER ','; --second record not ok
649+
16,6
650+
13,2
651+
\.
652+
653+
INSERT INTO pp VALUES (1,11), (2,12);
654+
INSERT INTO pp values (5,11); --error
655+
INSERT INTO pp values (6,11); --error
656+
COPY pp FROM STDIN WITH DELIMITER ','; --error, second record not ok
657+
1,11
658+
5,11
659+
\.
660+
661+
COPY pp FROM STDIN WITH DELIMITER ','; --error, second record not ok
662+
2,12
663+
6,11
664+
\.
665+
666+
RESET SESSION AUTHORIZATION;
667+
CREATE POLICY p1_pp ON pp FOR INSERT WITH CHECK(id > 4);
668+
SET SESSION AUTHORIZATION regress_rls_alice;
669+
INSERT INTO pp VALUES (5, 15), (6, 16); --ok
670+
INSERT INTO pp VALUES (4, 14); --error
671+
COPY pp FROM STDIN WITH DELIMITER ','; --third record will result error
672+
5,15
673+
6,16
674+
4,14
675+
\.
676+
677+
RESET SESSION AUTHORIZATION;
678+
DROP TABLE PP;
679+
621680
----- RECURSION ----
622681

623682
--
@@ -1795,8 +1854,10 @@ COPY copy_t FROM STDIN; --ok
17951854
SET SESSION AUTHORIZATION regress_rls_bob;
17961855
SET row_security TO OFF;
17971856
COPY copy_t FROM STDIN; --fail - would be affected by RLS.
1857+
\.
17981858
SET row_security TO ON;
1799-
COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
1859+
COPY copy_t FROM STDIN; --no error
1860+
\.
18001861

18011862
-- Check COPY FROM as user with permissions and BYPASSRLS
18021863
SET SESSION AUTHORIZATION regress_rls_exempt_user;
@@ -2154,6 +2215,9 @@ ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
21542215

21552216
-- Works fine
21562217
INSERT INTO r1 VALUES (10), (20);
2218+
COPY r1 FROM STDIN;
2219+
10
2220+
\.
21572221

21582222
-- No error, but no rows
21592223
TABLE r1;

0 commit comments

Comments
 (0)