-- 0.20.0 (second cut): verify() and verify_one() are the only door to a verdict, and a proof runs as the role that -- asserted it, by OID. In the first cut a verifier needed EXECUTE on the SECURITY DEFINER internals, and with them -- it forged 'verified' (_truth_set) and listed every proof and author (_truth_due with another verifier's name); -- and the author was a role NAME, so a dropped author's proof ran as whatever role took the name next. \set VERBOSITY terse CREATE EXTENSION pg_phasevec; SELECT phasevec.reader_register($n$r1$n$, repeat($h$a$h$, 64), 4) > 0 AS r1; r1 ---- t (1 row) CREATE TABLE notes (id int PRIMARY KEY, body text); INSERT INTO notes VALUES (1, $t$a claim$t$), (2, $t$another claim$t$); SELECT phasevec.watch($w$notes$w$, $w$notes$w$::regclass, $k$id$k$, $r$r.body$r$, $n$r1$n$) > 0 AS watch; watch ------- t (1 row) CREATE SCHEMA pv_private; CREATE TABLE pv_private.secret (v text); INSERT INTO pv_private.secret VALUES ($s$hunter2$s$); CREATE ROLE pv_admin; GRANT USAGE ON SCHEMA pv_private TO pv_admin; GRANT SELECT ON pv_private.secret TO pv_admin; CREATE ROLE pv_verifier; CREATE ROLE pv_low; GRANT USAGE ON SCHEMA phasevec TO pv_admin, pv_verifier, pv_low; GRANT SELECT ON notes TO pv_admin, pv_low; GRANT UPDATE ON notes TO pv_admin, pv_low; GRANT EXECUTE ON FUNCTION phasevec.assert(text, text, text, text, jsonb, timestamptz) TO pv_admin, pv_low; GRANT EXECUTE ON FUNCTION phasevec.verify(text, int, interval, interval, int, float8), phasevec.verify_one(text, text, int, float8) TO pv_verifier; SELECT id AS notes_id FROM phasevec.watches WHERE name = $w$notes$w$ \gset SELECT current_user AS su \gset -- (a) a verifier that may not act as the author cannot forge its verdict nor read its proof SET ROLE pv_admin; SELECT phasevec.assert($w$notes$w$, $k$1$k$, $p$(SELECT v FROM pv_private.secret) = 'wrong'$p$); assert -------- (1 row) RESET ROLE; -- the first cut's documented grants, where they still exist DO $d$ DECLARE f regprocedure; BEGIN FOR f IN SELECT p.oid::regprocedure FROM pg_proc p WHERE p.pronamespace = $n$phasevec$n$::regnamespace AND p.proname IN ($f$_watch$f$, $f$_truth_due$f$, $f$_truth_of$f$, $f$_truth_set$f$, $f$_prove_as$f$) LOOP EXECUTE format($g$GRANT EXECUTE ON FUNCTION %s TO pv_verifier$g$, f); END LOOP; END $d$; SET ROLE pv_verifier; SELECT phasevec.verify_one($w$notes$w$, $k$1$k$); ERROR: phasevec: role "pv_verifier" cannot run a proof as its author "pv_admin" SELECT key, proof, author FROM phasevec._truth_due(:notes_id, 100, interval $i$0$i$, :'su'::name); ERROR: function phasevec._truth_due(integer, integer, interval, name) does not exist at character 32 SELECT phasevec._truth_set(:notes_id, $k$1$k$, true, NULL, (SELECT bound FROM phasevec._truth_of(:notes_id, $k$1$k$))) AS forged; ERROR: column "bound" does not exist at character 67 -- the internals of this version, even granted, write and read nothing: they carry no rights of their own SELECT key FROM phasevec._truth_due(:notes_id, 100, interval $i$0$i$, $r$pv_admin$r$::regrole::oid); ERROR: permission denied for table truth SELECT phasevec._truth_set(:notes_id, $k$1$k$, $v$verified$v$, NULL, NULL, NULL); ERROR: permission denied for table truth RESET ROLE; SELECT key, verdict, phasevec._verdict(t) AS now FROM phasevec.truth t WHERE key = $k$1$k$; key | verdict | now -----+----------+---------- 1 | unproven | unproven (1 row) SELECT phasevec.verify_one($w$notes$w$, $k$1$k$) AS real_verdict; real_verdict -------------- refuted (1 row) -- (b) the author is a role, not a name: dropped, and its name given to another role, its proof runs as no one SET ROLE pv_low; SELECT phasevec.assert($w$notes$w$, $k$2$k$, $p$(SELECT v FROM pv_private.secret) = 'hunter2'$p$); assert -------- (1 row) RESET ROLE; SELECT phasevec.verify_one($w$notes$w$, $k$2$k$); verify_one ------------ refuted (1 row) DROP OWNED BY pv_low; DROP ROLE pv_low; SELECT key, asserted_by FROM phasevec.truth WHERE key = $k$2$k$; key | asserted_by -----+------------- 2 | pv_low (1 row) ALTER ROLE pv_admin RENAME TO pv_low; SELECT phasevec.verify_one($w$notes$w$, $k$2$k$); verify_one ------------ unproven (1 row) SELECT key, verdict, last_error FROM phasevec.truth WHERE key = $k$2$k$; key | verdict | last_error -----+----------+------------------------------------------------------------------------------------------------------------------------------------------------ 2 | unproven | the author of this proof is gone (dropped, or its name is another role's now): assert it again, or adopt it naming its author (phasevec.adopt) (1 row) ALTER ROLE pv_low RENAME TO pv_admin; -- the owner adopts it, naming the author: a superuser only for the keys it names (test/sql/truth_guard.sql) SELECT key, proof, former_author, adopted, note FROM phasevec.adopt($w$notes$w$, current_user::regrole); key | proof | former_author | adopted | note -----+-----------------------------------------------+---------------+---------+------------------------------------------------------------------------------------------ 2 | (SELECT v FROM pv_private.secret) = 'hunter2' | pv_low | f | not adopted: its author would be a superuser; name the key in superuser_keys to adopt it (1 row) SELECT key, former_author, adopted, note FROM phasevec.adopt($w$notes$w$, current_user::regrole, p_superuser_keys => ARRAY[$k$2$k$]); key | former_author | adopted | note -----+---------------+---------+-------------------------------------------- 2 | pv_low | t | adopted: it runs with a superuser's rights (1 row) SELECT key, asserted_by = current_user AS by_owner, phasevec.verify_one($w$notes$w$, key) FROM phasevec.truth WHERE key = $k$2$k$; key | by_owner | verify_one -----+----------+------------ 2 | t | verified (1 row) DROP TABLE notes; DROP SCHEMA pv_private CASCADE; NOTICE: drop cascades to table pv_private.secret DROP EXTENSION pg_phasevec; DROP OWNED BY pv_admin, pv_verifier; DROP ROLE pv_admin; DROP ROLE pv_verifier;