Report as revoked only what the read-back shows gone, and take back what the reader passed on (review of hq issue 337)
mesh/merge-gate pass: builds postgres, sent nowhere; no bus step; 2 wait(s) for a person; every machine composes with the change as it did without (4 of 4 …
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

A reader given a privilege WITH GRANT OPTION that passed it on made the
plain revoke fail with dependent privileges exist; the pass still listed the
table as revoked and logged it every minute. The pass now retries such a
change with CASCADE, which reaches only grants that came from the reader,
reads the grants back, reports as revoked only what is gone, and says what
is still held and why when anything is.
This commit is contained in:
jochen
2026-10-08 22:11:06 +02:00
parent 8bf50f7555
commit e0e97a96c4
4 changed files with 134 additions and 9 deletions
@@ -181,3 +181,51 @@ func TestLiveAGrantByAnotherGrantorIsTakenBackAndDefinersAreSaid(t *testing.T) {
}
}
}
// The reader was given a table WITH GRANT OPTION and passed it on. A plain revoke then fails with
// "dependent privileges exist" and changes nothing; the pass must take back the reader's grant and
// what the reader passed on (CASCADE reaches only grants that came from the reader), report as
// revoked only what the read-back shows gone, and say what is still held and why if anything is.
func TestLiveAGrantTheReaderPassedOnIsTakenBack(t *testing.T) {
c := liveClient(t)
freshStore(t, c)
defer dropStore(c)
defer func() { _, _ = c.Query(ctx, "", "DROP ROLE IF EXISTS mesh_test_onward") }()
if _, err := c.Query(ctx, "", "DROP ROLE IF EXISTS mesh_test_onward; CREATE ROLE mesh_test_onward"); err != nil {
t.Fatal(err)
}
for _, sql := range []string{
"REVOKE ALL ON vault FROM " + Ident(Reader),
"GRANT SELECT ON notes TO " + Ident(Reader) + " WITH GRANT OPTION",
"SET ROLE " + Ident(Reader) + "; GRANT SELECT ON notes TO mesh_test_onward; RESET ROLE",
} {
if _, err := c.Query(ctx, liveStore, sql); err != nil {
t.Fatal(sql, err)
}
}
var mine DatabaseReport
for _, db := range c.ReconcileReader(ctx, Declarations{}, "").Databases {
if db.Database == liveStore {
mine = db
}
}
if mine.Error != "" {
t.Fatal("left standing:", mine.Error)
}
if columnReadable(t, c, liveStore, "notes", "body") {
t.Fatalf("the reader still reads notes, and the pass said revoked %v", mine.Revoked)
}
if strings.Join(mine.Revoked, ",") != "public.notes" {
t.Fatalf("revoked %v", mine.Revoked)
}
r, err := c.Query(ctx, liveStore, "SELECT has_table_privilege('mesh_test_onward', 'notes', 'SELECT')")
if err != nil || cell(r.Rows[0], 0) != "f" {
t.Fatal("what the reader passed on survived:", r.Rows, err)
}
// The next pass finds nothing held, and says nothing revoked.
for _, db := range c.ReconcileReader(ctx, Declarations{}, "").Databases {
if db.Database == liveStore && (len(db.Revoked) > 0 || db.Error != "") {
t.Fatalf("%+v", db)
}
}
}