@@ -407,6 +407,262 @@ RemovePolicyById(Oid policy_id)
407
407
heap_close (pg_policy_rel , RowExclusiveLock );
408
408
}
409
409
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
+
410
666
/*
411
667
* CreatePolicy -
412
668
* handles the execution of the CREATE POLICY command.
0 commit comments