diff --git a/test/helpers/create.sql b/test/helpers/create.sql index 775a299..4d80567 100644 --- a/test/helpers/create.sql +++ b/test/helpers/create.sql @@ -11,7 +11,15 @@ GRANT USAGE ON SCHEMA tap TO :test_role; */ CREATE SCHEMA test AUTHORIZATION :test_role; -SET ROLE = :test_role; +/* + * SET SESSION AUTHORIZATION (not SET ROLE): it changes session_user too, not + * just current_user. Permission checks for a *further* SET ROLE (like the one + * test_factory's install does, and like issue #14's bug) are based on + * session_user's superuser status, not current_user's -- so a plain SET ROLE + * here would leave that one class of check silently bypassed for the rest of + * this file, since pg_regress always connects as a superuser. + */ +SET SESSION AUTHORIZATION :test_role; SET search_path = test, tap; CREATE TABLE customer( diff --git a/test/roles.sql b/test/roles.sql index 4ab7010..9f65807 100644 --- a/test/roles.sql +++ b/test/roles.sql @@ -10,4 +10,12 @@ -- test/install/load.sql only -- see its own comment. \set installer_role test_factory_installer +/* + * test/sql/security.sql only -- see its own comment. Deliberately not + * test_role: that role is set up by test/helpers/create.sql for other + * files, and this one exists specifically to have no setup beyond + * Postgres' own role defaults. + */ +\set bare_role test_factory_bare_user + -- vi: expandtab ts=2 sw=2 diff --git a/test/sql/security.sql b/test/sql/security.sql new file mode 100644 index 0000000..bf8273d --- /dev/null +++ b/test/sql/security.sql @@ -0,0 +1,68 @@ +\set ECHO none +\i test/helpers/setup.sql + +/* + * Prove the public tf.* API needs nothing beyond what a freshly-created, + * unprivileged login role already gets by default: no owned schema, no + * explicit GRANTs, and (deliberately) no membership in test_factory__owner. + * Everything it uses here (tf/_tf schema USAGE, EXECUTE on tf.* functions, + * CREATE TEMP TABLE) comes from either Postgres' own defaults or the GRANTs + * test_factory's install script makes to PUBLIC. + */ +SET ROLE = DEFAULT; +CREATE ROLE :bare_role; +/* + * USAGE on tap is a pgtap test-harness necessity (to call lives_ok() etc. + * below), not one of the grants under test here. + */ +GRANT USAGE ON SCHEMA tap TO :bare_role; +/* + * SET SESSION AUTHORIZATION, not SET ROLE: it changes session_user too, which + * is what a further SET ROLE's permission check actually looks at. A plain + * SET ROLE here would leave this session able to SET ROLE into anything + * (including test_factory__owner below) regardless of grants, since + * pg_regress always connects as a superuser. + */ +SET SESSION AUTHORIZATION :bare_role; + +CREATE TEMP TABLE widget( + widget_id serial PRIMARY KEY + , name text NOT NULL +); + +SELECT lives_ok( +$lives_ok$SELECT tf.register( + 'widget' + , array[ + row( + 'base' + , $$INSERT INTO widget VALUES (DEFAULT, 'gadget') RETURNING *$$ + )::tf.test_set + ] +);$lives_ok$ + , 'Bare, unprivileged role can register test data with zero extra grants' +); + +SELECT results_eq( + $$SELECT * FROM tf.get( NULL::widget, 'base' )$$ + , $$VALUES( 1, 'gadget' )$$ + , 'Bare, unprivileged role can create+fetch test data with zero extra grants' +); + +SELECT results_eq( + $$SELECT * FROM tf.get( NULL::widget, 'base' )$$ + , $$VALUES( 1, 'gadget' )$$ + , 'Bare, unprivileged role gets the cached row on a second call' +); + +-- Confirm role isolation still holds for a role that otherwise works fine +SELECT throws_ok( + $$SET ROLE test_factory__owner$$ + , '42501' + , NULL + , 'Bare role cannot SET ROLE into the extension owner role' +); + +ROLLBACK; + +-- vi: expandtab ts=2 sw=2