\set ECHO none \i test/setup.sql /* * Dynamically verify the permission model for the entire cat_tools public API: * - no_use_role (no cat_tools__usage grant) cannot use any type or execute any function * - use_role (has cat_tools__usage) can use all types and execute all functions * * Views are created as superuser so both roles can read them via explicit grants. */ CREATE TEMP VIEW cat_types AS SELECT 'cat_tools.' || typname AS type_name FROM pg_type t WHERE t.typnamespace = 'cat_tools'::regnamespace AND t.typtype IN ('e', 'c') -- enums and composite types ORDER BY typname ; GRANT SELECT ON cat_types TO :"use_role", :"no_use_role"; CREATE TEMP VIEW cat_functions AS SELECT p.oid, p.proname, pg_get_function_arguments(p.oid) AS args FROM pg_proc p WHERE p.pronamespace = 'cat_tools'::regnamespace ORDER BY proname, pg_get_function_arguments(p.oid) ; GRANT SELECT ON cat_functions TO :"use_role", :"no_use_role"; SELECT plan( (SELECT count(*)::int FROM cat_types) * 2 -- no_use denied + use allowed + (SELECT count(*)::int FROM cat_functions) * 2 -- no_use denied + use allowed + 2 -- _cat_tools schema access (no_use denied + use allowed) + 1 -- search_path still clean (test/finish.sql) ); /* * Function privilege checks via pg_catalog privilege functions. * has_function_privilege(user, func, priv) checks the named role's privilege * including inherited roles, independent of the current session role. */ SELECT is( has_function_privilege(:'no_use_role', oid, 'EXECUTE') , false , format('Permission denied trying to execute cat_tools.%s(%s)', proname, args) ) FROM cat_functions ; SELECT is( has_function_privilege(:'use_role', oid, 'EXECUTE') , true , format('Permission granted to execute cat_tools.%s(%s)', proname, args) ) FROM cat_functions ; /* * Type access checks via role switching. * Attempts actual casts to verify enforcement at the SQL level. */ SET LOCAL ROLE :"no_use_role"; SELECT throws_ok( format('SELECT NULL::%s', type_name) , '42501' , NULL , format('Permission denied trying to use type %s', type_name) ) FROM cat_types ; SET LOCAL ROLE :"use_role"; SELECT lives_ok( format('SELECT NULL::%s', type_name) , format('Permission granted to use type %s', type_name) ) FROM cat_types ; /* * _cat_tools schema access checks. * * Schema-level USAGE is the primary barrier: no_use_role cannot resolve any * object name in _cat_tools, making its functions effectively private. * function__arg_to_regprocedure and function__drop_temp also have explicit * REVOKE EXECUTE FROM PUBLIC for defense in depth. */ SELECT is( has_schema_privilege(:'no_use_role', '_cat_tools', 'USAGE') , false , 'no_use_role has no USAGE on _cat_tools schema' ); SELECT is( has_schema_privilege(:'use_role', '_cat_tools', 'USAGE') , true , 'use_role has USAGE on _cat_tools schema' ); \i test/finish.sql -- vi: expandtab ts=2 sw=2