Skip to content

Commit 833728d

Browse files
committed
Handle policies during DROP OWNED BY
DROP OWNED BY handled GRANT-based ACLs but was not removing roles from policies. Fix that by having DROP OWNED BY remove the role specified from the list of roles the policy (or policies) apply to, or the entire policy (or policies) if it only applied to the role specified. As with ACLs, the DROP OWNED BY caller must have permission to modify the policy or a WARNING is thrown and no change is made to the policy.
1 parent 4fcf484 commit 833728d

File tree

5 files changed

+303
-0
lines changed

5 files changed

+303
-0
lines changed

src/backend/catalog/pg_shdepend.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "commands/defrem.h"
5151
#include "commands/event_trigger.h"
5252
#include "commands/extension.h"
53+
#include "commands/policy.h"
5354
#include "commands/proclang.h"
5455
#include "commands/schemacmds.h"
5556
#include "commands/tablecmds.h"
@@ -1245,6 +1246,18 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
12451246
sdepForm->classid,
12461247
sdepForm->objid);
12471248
break;
1249+
case SHARED_DEPENDENCY_POLICY:
1250+
/* If unable to remove role from policy, remove policy. */
1251+
if (!RemoveRoleFromObjectPolicy(roleid,
1252+
sdepForm->classid,
1253+
sdepForm->objid))
1254+
{
1255+
obj.classId = sdepForm->classid;
1256+
obj.objectId = sdepForm->objid;
1257+
obj.objectSubId = sdepForm->objsubid;
1258+
add_exact_object_address(&obj, deleteobjs);
1259+
}
1260+
break;
12481261
case SHARED_DEPENDENCY_OWNER:
12491262
/* If a local object, save it for deletion below */
12501263
if (sdepForm->dbid == MyDatabaseId)

src/backend/commands/policy.c

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,262 @@ RemovePolicyById(Oid policy_id)
407407
heap_close(pg_policy_rel, RowExclusiveLock);
408408
}
409409

410+
/*
411+
* RemoveRoleFromObjectPolicy -
412+
* remove a role from a policy by its OID. If the role is not a member of
413+
* the policy then an error is raised. False is returned to indicate that
414+
* the role could not be removed due to being the only role on the policy
415+
* and therefore the entire policy should be removed.
416+
*
417+
* Note that a warning will be thrown and true will be returned on a
418+
* permission error, as the policy should not be removed in that case.
419+
*
420+
* roleid - the oid of the role to remove
421+
* classid - should always be PolicyRelationId
422+
* policy_id - the oid of the policy.
423+
*/
424+
bool
425+
RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id)
426+
{
427+
Relation pg_policy_rel;
428+
SysScanDesc sscan;
429+
ScanKeyData skey[1];
430+
HeapTuple tuple;
431+
Oid relid;
432+
Relation rel;
433+
ArrayType *policy_roles;
434+
int num_roles;
435+
Datum roles_datum;
436+
bool attr_isnull;
437+
bool noperm = true;
438+
439+
Assert(classid == PolicyRelationId);
440+
441+
pg_policy_rel = heap_open(PolicyRelationId, RowExclusiveLock);
442+
443+
/*
444+
* Find the policy to update.
445+
*/
446+
ScanKeyInit(&skey[0],
447+
ObjectIdAttributeNumber,
448+
BTEqualStrategyNumber, F_OIDEQ,
449+
ObjectIdGetDatum(policy_id));
450+
451+
sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true,
452+
NULL, 1, skey);
453+
454+
tuple = systable_getnext(sscan);
455+
456+
/* Raise an error if we don't find the policy. */
457+
if (!HeapTupleIsValid(tuple))
458+
elog(ERROR, "could not find tuple for policy %u", policy_id);
459+
460+
/*
461+
* Open and exclusive-lock the relation the policy belongs to.
462+
*/
463+
relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
464+
465+
rel = relation_open(relid, AccessExclusiveLock);
466+
467+
if (rel->rd_rel->relkind != RELKIND_RELATION)
468+
ereport(ERROR,
469+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
470+
errmsg("\"%s\" is not a table",
471+
RelationGetRelationName(rel))));
472+
473+
if (!allowSystemTableMods && IsSystemRelation(rel))
474+
ereport(ERROR,
475+
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
476+
errmsg("permission denied: \"%s\" is a system catalog",
477+
RelationGetRelationName(rel))));
478+
479+
/* Get the current set of roles */
480+
roles_datum = heap_getattr(tuple,
481+
Anum_pg_policy_polroles,
482+
RelationGetDescr(pg_policy_rel),
483+
&attr_isnull);
484+
485+
Assert(!attr_isnull);
486+
487+
policy_roles = DatumGetArrayTypePCopy(roles_datum);
488+
489+
/* We should be removing exactly one entry from the roles array */
490+
num_roles = ARR_DIMS(policy_roles)[0] - 1;
491+
492+
Assert(num_roles >= 0);
493+
494+
/* Must own relation. */
495+
if (pg_class_ownercheck(relid, GetUserId()))
496+
noperm = false; /* user is allowed to modify this policy */
497+
else
498+
ereport(WARNING,
499+
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
500+
errmsg("role \"%s\" could not be removed from policy \"%s\" on \"%s\"",
501+
GetUserNameFromId(roleid, false),
502+
NameStr(((Form_pg_policy) GETSTRUCT(tuple))->polname),
503+
RelationGetRelationName(rel))));
504+
505+
/*
506+
* If multiple roles exist on this policy, then remove the one we were
507+
* asked to and leave the rest.
508+
*/
509+
if (!noperm && num_roles > 0)
510+
{
511+
int i, j;
512+
Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
513+
Datum *role_oids;
514+
char *qual_value;
515+
Node *qual_expr;
516+
List *qual_parse_rtable = NIL;
517+
char *with_check_value;
518+
Node *with_check_qual;
519+
List *with_check_parse_rtable = NIL;
520+
Datum values[Natts_pg_policy];
521+
bool isnull[Natts_pg_policy];
522+
bool replaces[Natts_pg_policy];
523+
Datum value_datum;
524+
ArrayType *role_ids;
525+
HeapTuple new_tuple;
526+
ObjectAddress target;
527+
ObjectAddress myself;
528+
529+
/* zero-clear */
530+
memset(values, 0, sizeof(values));
531+
memset(replaces, 0, sizeof(replaces));
532+
memset(isnull, 0, sizeof(isnull));
533+
534+
/*
535+
* All of the dependencies will be removed from the policy and then
536+
* re-added. In order to get them correct, we need to extract out
537+
* the expressions in the policy and construct a parsestate just
538+
* enough to build the range table(s) to then pass to
539+
* recordDependencyOnExpr().
540+
*/
541+
542+
/* Get policy qual, to update dependencies */
543+
value_datum = heap_getattr(tuple, Anum_pg_policy_polqual,
544+
RelationGetDescr(pg_policy_rel), &attr_isnull);
545+
if (!attr_isnull)
546+
{
547+
ParseState *qual_pstate;
548+
549+
/* parsestate is built just to build the range table */
550+
qual_pstate = make_parsestate(NULL);
551+
552+
qual_value = TextDatumGetCString(value_datum);
553+
qual_expr = stringToNode(qual_value);
554+
555+
/* Add this rel to the parsestate's rangetable, for dependencies */
556+
addRangeTableEntryForRelation(qual_pstate, rel, NULL, false, false);
557+
558+
qual_parse_rtable = qual_pstate->p_rtable;
559+
free_parsestate(qual_pstate);
560+
}
561+
else
562+
qual_expr = NULL;
563+
564+
/* Get WITH CHECK qual, to update dependencies */
565+
value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
566+
RelationGetDescr(pg_policy_rel), &attr_isnull);
567+
if (!attr_isnull)
568+
{
569+
ParseState *with_check_pstate;
570+
571+
/* parsestate is built just to build the range table */
572+
with_check_pstate = make_parsestate(NULL);
573+
574+
with_check_value = TextDatumGetCString(value_datum);
575+
with_check_qual = stringToNode(with_check_value);
576+
577+
/* Add this rel to the parsestate's rangetable, for dependencies */
578+
addRangeTableEntryForRelation(with_check_pstate, rel, NULL, false,
579+
false);
580+
581+
with_check_parse_rtable = with_check_pstate->p_rtable;
582+
free_parsestate(with_check_pstate);
583+
}
584+
else
585+
with_check_qual = NULL;
586+
587+
/* Rebuild the roles array to then update the pg_policy tuple with */
588+
role_oids = (Datum *) palloc(num_roles * sizeof(Datum));
589+
for (i = 0, j = 0; i < ARR_DIMS(policy_roles)[0]; i++)
590+
/* Copy over all of the roles which are not the one being removed */
591+
if (roles[i] != roleid)
592+
role_oids[j++] = ObjectIdGetDatum(roles[i]);
593+
594+
/* We should have only removed the one role */
595+
Assert(j == num_roles);
596+
597+
/* This is the array for the new tuple */
598+
role_ids = construct_array(role_oids, num_roles, OIDOID,
599+
sizeof(Oid), true, 'i');
600+
601+
replaces[Anum_pg_policy_polroles - 1] = true;
602+
values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids);
603+
604+
new_tuple = heap_modify_tuple(tuple,
605+
RelationGetDescr(pg_policy_rel),
606+
values, isnull, replaces);
607+
simple_heap_update(pg_policy_rel, &new_tuple->t_self, new_tuple);
608+
609+
/* Update Catalog Indexes */
610+
CatalogUpdateIndexes(pg_policy_rel, new_tuple);
611+
612+
/* Remove all old dependencies. */
613+
deleteDependencyRecordsFor(PolicyRelationId, policy_id, false);
614+
615+
/* Record the new set of dependencies */
616+
target.classId = RelationRelationId;
617+
target.objectId = relid;
618+
target.objectSubId = 0;
619+
620+
myself.classId = PolicyRelationId;
621+
myself.objectId = policy_id;
622+
myself.objectSubId = 0;
623+
624+
recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
625+
626+
if (qual_expr)
627+
recordDependencyOnExpr(&myself, qual_expr, qual_parse_rtable,
628+
DEPENDENCY_NORMAL);
629+
630+
if (with_check_qual)
631+
recordDependencyOnExpr(&myself, with_check_qual,
632+
with_check_parse_rtable,
633+
DEPENDENCY_NORMAL);
634+
635+
/* Remove all the old shared dependencies (roles) */
636+
deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0);
637+
638+
/* Record the new shared dependencies (roles) */
639+
target.classId = AuthIdRelationId;
640+
target.objectSubId = 0;
641+
for (i = 0; i < num_roles; i++)
642+
{
643+
target.objectId = DatumGetObjectId(role_oids[i]);
644+
/* no need for dependency on the public role */
645+
if (target.objectId != ACL_ID_PUBLIC)
646+
recordSharedDependencyOn(&myself, &target,
647+
SHARED_DEPENDENCY_POLICY);
648+
}
649+
650+
InvokeObjectPostAlterHook(PolicyRelationId, policy_id, 0);
651+
652+
heap_freetuple(new_tuple);
653+
654+
/* Invalidate Relation Cache */
655+
CacheInvalidateRelcache(rel);
656+
}
657+
658+
/* Clean up. */
659+
systable_endscan(sscan);
660+
relation_close(rel, AccessExclusiveLock);
661+
heap_close(pg_policy_rel, RowExclusiveLock);
662+
663+
return(noperm || num_roles > 0);
664+
}
665+
410666
/*
411667
* CreatePolicy -
412668
* handles the execution of the CREATE POLICY command.

src/include/commands/policy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ extern void RelationBuildRowSecurity(Relation relation);
2323

2424
extern void RemovePolicyById(Oid policy_id);
2525

26+
extern bool RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid objid);
27+
2628
extern ObjectAddress CreatePolicy(CreatePolicyStmt *stmt);
2729
extern ObjectAddress AlterPolicy(AlterPolicyStmt *stmt);
2830

src/test/regress/expected/rowsecurity.out

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3289,6 +3289,20 @@ SELECT count(*) = 0 FROM pg_depend
32893289
t
32903290
(1 row)
32913291

3292+
-- DROP OWNED BY testing
3293+
RESET SESSION AUTHORIZATION;
3294+
CREATE ROLE dob_role1;
3295+
CREATE ROLE dob_role2;
3296+
CREATE TABLE dob_t1 (c1 int);
3297+
CREATE POLICY p1 ON dob_t1 TO dob_role1 USING (true);
3298+
DROP OWNED BY dob_role1;
3299+
DROP POLICY p1 ON dob_t1; -- should fail, already gone
3300+
ERROR: policy "p1" for table "dob_t1" does not exist
3301+
CREATE POLICY p1 ON dob_t1 TO dob_role1,dob_role2 USING (true);
3302+
DROP OWNED BY dob_role1;
3303+
DROP POLICY p1 ON dob_t1; -- should succeed
3304+
DROP USER dob_role1;
3305+
DROP USER dob_role2;
32923306
--
32933307
-- Clean up objects
32943308
--

src/test/regress/sql/rowsecurity.sql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,6 +1520,24 @@ SELECT count(*) = 0 FROM pg_depend
15201520
WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')
15211521
AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');
15221522

1523+
-- DROP OWNED BY testing
1524+
RESET SESSION AUTHORIZATION;
1525+
1526+
CREATE ROLE dob_role1;
1527+
CREATE ROLE dob_role2;
1528+
1529+
CREATE TABLE dob_t1 (c1 int);
1530+
1531+
CREATE POLICY p1 ON dob_t1 TO dob_role1 USING (true);
1532+
DROP OWNED BY dob_role1;
1533+
DROP POLICY p1 ON dob_t1; -- should fail, already gone
1534+
1535+
CREATE POLICY p1 ON dob_t1 TO dob_role1,dob_role2 USING (true);
1536+
DROP OWNED BY dob_role1;
1537+
DROP POLICY p1 ON dob_t1; -- should succeed
1538+
1539+
DROP USER dob_role1;
1540+
DROP USER dob_role2;
15231541

15241542
--
15251543
-- Clean up objects

0 commit comments

Comments
 (0)