\set ECHO none \i test/setup.sql \set s cat_tools SET LOCAL ROLE :"use_role"; SELECT plan( (1 + 2 + 3 * array_length(cat_tools.enum_range('cat_tools.routine_type'), 1)) + (1 + 2 + 3 * array_length(cat_tools.enum_range('cat_tools.routine_argument_mode'), 1)) + (1 + 2 + 3 * array_length(cat_tools.enum_range('cat_tools.routine_volatility'), 1)) + (1 + 2 + 3 * array_length(cat_tools.enum_range('cat_tools.routine_parallel_safety'), 1)) + 2 -- no_use_role access denied for parse helpers (throws_ok via func_calls) + 1 -- security check when current_user != session_user + 1 -- create pg_temp.test_function + 1 -- create pg_temp.named_function + 4 -- routine__parse_arg_types() tests + 1 -- isnt_definer: routine__parse_arg_types + 1 -- isnt_definer: routine__parse_arg_types_text + 4 -- routine__arg_types() tests + 4 -- routine__arg_types_text() tests + 4 -- routine__parse_arg_names() tests + 1 -- isnt_definer: routine__parse_arg_names + 1 -- isnt_definer: routine__parse_arg_names_text + 3 -- routine__arg_names() tests + 4 -- routine__arg_names_text() tests + 1 -- search_path still clean (test/finish.sql) ); \set kind type \set char_col prokind \set sample_char f \set sample_text function \i test/helpers/enum_mapping.sql \set kind argument_mode \set char_col proargmode \set sample_char i \set sample_text in \i test/helpers/enum_mapping.sql \set kind volatility \set char_col provolatile \set sample_char i \set sample_text immutable \i test/helpers/enum_mapping.sql \set kind parallel_safety \set char_col proparallel \set sample_char s \set sample_text safe \i test/helpers/enum_mapping.sql CREATE TEMP VIEW func_calls AS SELECT * FROM (VALUES ('routine__parse_arg_types'::name, $$'x'$$::text) , ('routine__parse_arg_names'::name, $$'x'$$::text) ) v(fname, args) ; GRANT SELECT ON func_calls TO public; SET LOCAL ROLE :"no_use_role"; SELECT throws_ok( format( $$SELECT %I.%I( %L )$$ , :'s', fname , args ) , '42501' , NULL , 'Verify public has no perms' ) FROM func_calls ; /* * Test that the security check works when current_user != session_user. * SET LOCAL ROLE makes current_user = use_role while session_user = superuser, * so current_user != session_user, and the function should throw 28000. */ SET LOCAL ROLE :"use_role"; SELECT throws_ok( $$SELECT cat_tools.routine__parse_arg_types('int')$$ , '28000' , 'potential use of SECURITY DEFINER detected' , 'Security check should prevent execution when current_user != session_user' ); /* * SET SESSION AUTHORIZATION satisfies the current_user = session_user check * required by the parse helper security guard. All functional tests below * run under this authorization. */ SET SESSION AUTHORIZATION :"use_role"; /* * routine__parse_arg_types / routine__parse_arg_types_text */ SELECT is( :s.routine__parse_arg_types($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) , '{int,int[],anyelement,boolean}'::regtype[] , 'Verify routine__parse_arg_types() with INOUT and OUT' ); SELECT is( :s.routine__parse_arg_types($$IN in_int int, INOUT inout_int_array int[], anyarray, anyelement, boolean DEFAULT false$$) , '{int,int[],anyarray,anyelement,boolean}'::regtype[] , 'Verify routine__parse_arg_types() with just INOUT' ); SELECT is( :s.routine__parse_arg_types($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) , '{int,anyarray,anyelement,boolean}'::regtype[] , 'Verify routine__parse_arg_types() with just OUT' ); SELECT is( :s.routine__parse_arg_types($$anyelement, "char", pg_class, VARIADIC boolean[]$$) , '{anyelement,"\"char\"",pg_class,boolean[]}'::regtype[] , 'Verify routine__parse_arg_types() with only inputs' ); /* * CRITICAL SECURITY TESTS: public routine__ functions must NOT be SECURITY DEFINER. * If they were, they could be exploited for SQL injection since they execute * dynamic SQL with elevated privileges. */ \set f routine__parse_arg_types SELECT string_to_array('text', ', ') AS _f_args \gset SELECT isnt_definer(:'s', :'f', :'_f_args'::name[]); \set f routine__parse_arg_types_text SELECT isnt_definer(:'s', :'f', :'_f_args'::name[]); -- Create pg_temp.test_function now that we have a stable session_user. \set args 'anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]' SELECT lives_ok( format( $$CREATE FUNCTION pg_temp.test_function(%s) LANGUAGE plpgsql AS $body$BEGIN NULL; END$body$;$$ , :'args' ) , format('Create pg_temp.test_function(%s)', :'args') ); /* * routine__arg_types / routine__arg_types_text */ SELECT is( :s.routine__arg_types(:s.regprocedure('pg_temp.test_function', :'args')) , '{anyarray,pg_class,integer,boolean[]}'::regtype[] , 'Verify routine__arg_types() returns all argument types' ); SELECT is( :s.routine__arg_types('array_length(anyarray,integer)'::regprocedure) , '{anyarray,integer}'::regtype[] , 'Verify routine__arg_types() with IN arguments only' ); SELECT is( :s.routine__arg_types('pg_backend_pid()'::regprocedure) , '{}'::regtype[] , 'Verify routine__arg_types() with no arguments' ); SELECT is( :s.routine__arg_types('concat("any")'::regprocedure) , '{"\"any\""}'::regtype[] , 'Verify routine__arg_types() with VARIADIC argument' ); SELECT is( :s.routine__arg_types_text(:s.regprocedure('pg_temp.test_function', :'args')) , 'anyarray, pg_class, integer, boolean[]' , 'Verify routine__arg_types_text() formatting' ); SELECT is( :s.routine__arg_types_text('array_length(anyarray,integer)'::regprocedure) , 'anyarray, integer' , 'Verify routine__arg_types_text() with simple types' ); SELECT is( :s.routine__arg_types_text('pg_backend_pid()'::regprocedure) , '' , 'Verify routine__arg_types_text() with no arguments' ); SELECT is( :s.routine__arg_types_text('concat("any")'::regprocedure) , '"any"' , 'Verify routine__arg_types_text() with VARIADIC' ); /* * routine__parse_arg_names / routine__parse_arg_names_text */ SELECT is( :s.routine__parse_arg_names($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) , '{in_int,inout_int_array,NULL,NULL}'::text[] , 'Verify routine__parse_arg_names() with INOUT and OUT' ); SELECT is( :s.routine__parse_arg_names($$IN in_int int, INOUT inout_int_array int[], anyarray, anyelement, boolean DEFAULT false$$) , '{in_int,inout_int_array,NULL,NULL,NULL}'::text[] , 'Verify routine__parse_arg_names() with just INOUT' ); SELECT is( :s.routine__parse_arg_names($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) , '{in_int,NULL,NULL,NULL}'::text[] , 'Verify routine__parse_arg_names() with just OUT' ); SELECT is( :s.routine__parse_arg_names($$anyelement, "char", pg_class, VARIADIC boolean[]$$) , '{NULL,NULL,NULL,NULL}'::text[] , 'Verify routine__parse_arg_names() with only inputs' ); \set f routine__parse_arg_names SELECT isnt_definer(:'s', :'f', :'_f_args'::name[]); \set f routine__parse_arg_names_text SELECT isnt_definer(:'s', :'f', :'_f_args'::name[]); /* * routine__arg_names / routine__arg_names_text */ -- Create pg_temp.named_function just before it is first used below. \set named_args 'input_val int, INOUT inout_val text, OUT output_val boolean' SELECT lives_ok( format( $$CREATE FUNCTION pg_temp.named_function(%s) LANGUAGE plpgsql AS $body$BEGIN output_val := true; END$body$;$$ , :'named_args' ) , format('Create pg_temp.named_function(%s)', :'named_args') ); SELECT is( :s.routine__arg_names(:s.regprocedure('pg_temp.test_function', :'args')) , '{NULL,NULL,NULL,NULL}'::text[] , 'Verify routine__arg_names() returns argument names (unnamed function)' ); SELECT is( :s.routine__arg_names(:s.regprocedure('pg_temp.named_function', :'named_args')) , '{input_val,inout_val}'::text[] , 'Verify routine__arg_names() with named arguments' ); SELECT is( :s.routine__arg_names('pg_backend_pid()'::regprocedure) , '{}'::text[] , 'Verify routine__arg_names() with no arguments' ); SELECT is( :s.routine__arg_names_text(:s.regprocedure('pg_temp.named_function', :'named_args')) , 'input_val, inout_val' , 'Verify routine__arg_names_text() formatting' ); SELECT is( :s.routine__arg_names_text(:s.regprocedure('pg_temp.test_function', :'args')) , '' , 'Verify routine__arg_names_text() with unnamed arguments' ); SELECT is( :s.routine__arg_names_text('array_length(anyarray,integer)'::regprocedure) , '' , 'Verify routine__arg_names_text() with built-in function' ); SELECT is( :s.routine__arg_names_text('pg_backend_pid()'::regprocedure) , '' , 'Verify routine__arg_names_text() with no arguments' ); \i test/finish.sql -- vi: expandtab ts=2 sw=2