diff --git a/CHANGELOG.md b/CHANGELOG.md index 59dbd845b..6fe15841e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ accidentally triggering the load of a previous DB version.** * #5433 Fix join rte in CAggs with joins * #5543 Copy scheduled_jobs list before sorting it * #5570 Improve interpolate error message on datatype mismatch +* #5558 Use regrole for job owner **Thanks** * @nikolaps for reporting an issue with the COPY fetcher diff --git a/sql/job_error_log_retention.sql b/sql/job_error_log_retention.sql index 662b8ea54..284891f68 100644 --- a/sql/job_error_log_retention.sql +++ b/sql/job_error_log_retention.sql @@ -65,7 +65,7 @@ VALUES INTERVAL '1h', '_timescaledb_internal', 'policy_job_error_retention', - CURRENT_ROLE, + current_role::regrole, true, '{"drop_after":"1 month"}', '_timescaledb_internal', diff --git a/sql/pre_install/tables.sql b/sql/pre_install/tables.sql index 48868fe95..be023ef85 100644 --- a/sql/pre_install/tables.sql +++ b/sql/pre_install/tables.sql @@ -280,7 +280,7 @@ CREATE TABLE _timescaledb_config.bgw_job ( retry_period interval NOT NULL, proc_schema name NOT NULL, proc_name name NOT NULL, - owner name NOT NULL DEFAULT CURRENT_ROLE, + owner regrole NOT NULL DEFAULT current_role::regrole, scheduled bool NOT NULL DEFAULT TRUE, fixed_schedule bool not null default true, initial_start timestamptz, diff --git a/sql/updates/latest-dev.sql b/sql/updates/latest-dev.sql index bd9766913..e9520aa7d 100644 --- a/sql/updates/latest-dev.sql +++ b/sql/updates/latest-dev.sql @@ -57,3 +57,74 @@ ALTER FUNCTION _timescaledb_internal.compressed_data_recv(internal) SET SCHEMA _ ALTER FUNCTION _timescaledb_internal.rxid_in(cstring) SET SCHEMA _timescaledb_functions; ALTER FUNCTION _timescaledb_internal.rxid_out(@extschema@.rxid) SET SCHEMA _timescaledb_functions; + +-- drop dependent views +DROP VIEW IF EXISTS timescaledb_information.job_errors; +DROP VIEW IF EXISTS timescaledb_information.job_stats; +DROP VIEW IF EXISTS timescaledb_information.jobs; +DROP VIEW IF EXISTS timescaledb_experimental.policies; + +ALTER TABLE _timescaledb_config.bgw_job + ALTER COLUMN owner DROP DEFAULT, + ALTER COLUMN owner TYPE regrole USING owner::regrole, + ALTER COLUMN owner SET DEFAULT current_role::regrole; +CREATE TABLE _timescaledb_config.bgw_job_tmp AS SELECT * FROM _timescaledb_config.bgw_job; + +ALTER EXTENSION timescaledb DROP TABLE _timescaledb_config.bgw_job; +ALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_config.bgw_job_id_seq; +ALTER TABLE _timescaledb_internal.bgw_job_stat + DROP CONSTRAINT IF EXISTS bgw_job_stat_job_id_fkey; +ALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats + DROP CONSTRAINT IF EXISTS bgw_policy_chunk_stats_job_id_fkey; +CREATE TABLE _timescaledb_internal.tmp_bgw_job_seq_value AS + SELECT last_value, is_called FROM _timescaledb_config.bgw_job_id_seq; +DROP TABLE _timescaledb_config.bgw_job; + +CREATE SEQUENCE _timescaledb_config.bgw_job_id_seq MINVALUE 1000; +SELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job_id_seq', ''); +SELECT pg_catalog.setval('_timescaledb_config.bgw_job_id_seq', last_value, is_called) + FROM _timescaledb_internal.tmp_bgw_job_seq_value; +DROP TABLE _timescaledb_internal.tmp_bgw_job_seq_value; + +CREATE TABLE _timescaledb_config.bgw_job ( + id integer NOT NULL DEFAULT nextval('_timescaledb_config.bgw_job_id_seq'), + application_name name NOT NULL, + schedule_interval interval NOT NULL, + max_runtime interval NOT NULL, + max_retries integer NOT NULL, + retry_period interval NOT NULL, + proc_schema name NOT NULL, + proc_name name NOT NULL, + owner regrole NOT NULL DEFAULT current_role::regrole, + scheduled bool NOT NULL DEFAULT TRUE, + fixed_schedule bool not null default true, + initial_start timestamptz, + hypertable_id integer, + config jsonb, + check_schema name, + check_name name, + timezone text, + CONSTRAINT bgw_job_pkey PRIMARY KEY (id), + CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE +); + +ALTER SEQUENCE _timescaledb_config.bgw_job_id_seq OWNED BY _timescaledb_config.bgw_job.id; +CREATE INDEX bgw_job_proc_hypertable_id_idx + ON _timescaledb_config.bgw_job(proc_schema,proc_name,hypertable_id); +INSERT INTO _timescaledb_config.bgw_job + SELECT * FROM _timescaledb_config.bgw_job_tmp ORDER BY id; +DROP TABLE _timescaledb_config.bgw_job_tmp; +ALTER TABLE _timescaledb_internal.bgw_job_stat + ADD CONSTRAINT bgw_job_stat_job_id_fkey + FOREIGN KEY(job_id) + REFERENCES _timescaledb_config.bgw_job(id) + ON DELETE CASCADE; +ALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats + ADD CONSTRAINT bgw_policy_chunk_stats_job_id_fkey + FOREIGN KEY(job_id) + REFERENCES _timescaledb_config.bgw_job(id) + ON DELETE CASCADE; + +SELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job', 'WHERE id >= 1000'); +GRANT SELECT ON _timescaledb_config.bgw_job TO PUBLIC; +GRANT SELECT ON _timescaledb_config.bgw_job_id_seq TO PUBLIC; diff --git a/sql/updates/reverse-dev.sql b/sql/updates/reverse-dev.sql index 908ebd4e1..2d40b5aaa 100644 --- a/sql/updates/reverse-dev.sql +++ b/sql/updates/reverse-dev.sql @@ -202,3 +202,73 @@ BEGIN END $BODY$ SET search_path TO pg_catalog, pg_temp; +-- drop dependent views +DROP VIEW IF EXISTS timescaledb_information.job_errors; +DROP VIEW IF EXISTS timescaledb_information.job_stats; +DROP VIEW IF EXISTS timescaledb_information.jobs; +DROP VIEW IF EXISTS timescaledb_experimental.policies; + +ALTER TABLE _timescaledb_config.bgw_job + ALTER COLUMN owner DROP DEFAULT, + ALTER COLUMN owner TYPE name USING owner::name, + ALTER COLUMN owner SET DEFAULT current_role; +CREATE TABLE _timescaledb_config.bgw_job_tmp AS SELECT * FROM _timescaledb_config.bgw_job; + +ALTER EXTENSION timescaledb DROP TABLE _timescaledb_config.bgw_job; +ALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_config.bgw_job_id_seq; +ALTER TABLE _timescaledb_internal.bgw_job_stat + DROP CONSTRAINT IF EXISTS bgw_job_stat_job_id_fkey; +ALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats + DROP CONSTRAINT IF EXISTS bgw_policy_chunk_stats_job_id_fkey; +CREATE TABLE _timescaledb_internal.tmp_bgw_job_seq_value AS + SELECT last_value, is_called FROM _timescaledb_config.bgw_job_id_seq; +DROP TABLE _timescaledb_config.bgw_job; + +CREATE SEQUENCE _timescaledb_config.bgw_job_id_seq MINVALUE 1000; +SELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job_id_seq', ''); +SELECT pg_catalog.setval('_timescaledb_config.bgw_job_id_seq', last_value, is_called) + FROM _timescaledb_internal.tmp_bgw_job_seq_value; +DROP TABLE _timescaledb_internal.tmp_bgw_job_seq_value; + +CREATE TABLE _timescaledb_config.bgw_job ( + id integer NOT NULL DEFAULT nextval('_timescaledb_config.bgw_job_id_seq'), + application_name name NOT NULL, + schedule_interval interval NOT NULL, + max_runtime interval NOT NULL, + max_retries integer NOT NULL, + retry_period interval NOT NULL, + proc_schema name NOT NULL, + proc_name name NOT NULL, + owner name NOT NULL DEFAULT current_role, + scheduled bool NOT NULL DEFAULT TRUE, + fixed_schedule bool not null default true, + initial_start timestamptz, + hypertable_id integer, + config jsonb, + check_schema name, + check_name name, + timezone text, + CONSTRAINT bgw_job_pkey PRIMARY KEY (id), + CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE +); + +ALTER SEQUENCE _timescaledb_config.bgw_job_id_seq OWNED BY _timescaledb_config.bgw_job.id; +CREATE INDEX bgw_job_proc_hypertable_id_idx + ON _timescaledb_config.bgw_job(proc_schema,proc_name,hypertable_id); +INSERT INTO _timescaledb_config.bgw_job + SELECT * FROM _timescaledb_config.bgw_job_tmp ORDER BY id; +DROP TABLE _timescaledb_config.bgw_job_tmp; +ALTER TABLE _timescaledb_internal.bgw_job_stat + ADD CONSTRAINT bgw_job_stat_job_id_fkey + FOREIGN KEY(job_id) + REFERENCES _timescaledb_config.bgw_job(id) + ON DELETE CASCADE; +ALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats + ADD CONSTRAINT bgw_policy_chunk_stats_job_id_fkey + FOREIGN KEY(job_id) + REFERENCES _timescaledb_config.bgw_job(id) + ON DELETE CASCADE; + +SELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job', 'WHERE id >= 1000'); +GRANT SELECT ON _timescaledb_config.bgw_job TO PUBLIC; +GRANT SELECT ON _timescaledb_config.bgw_job_id_seq TO PUBLIC; diff --git a/sql/with_telemetry.sql b/sql/with_telemetry.sql index a41e3cbf0..2d57e2395 100644 --- a/sql/with_telemetry.sql +++ b/sql/with_telemetry.sql @@ -7,5 +7,5 @@ CREATE OR REPLACE FUNCTION @extschema@.get_telemetry_report() RETURNS jsonb LANGUAGE C STABLE PARALLEL SAFE; INSERT INTO _timescaledb_config.bgw_job (id, application_name, schedule_interval, max_runtime, max_retries, retry_period, proc_schema, proc_name, owner, scheduled, fixed_schedule) VALUES -(1, 'Telemetry Reporter [1]', INTERVAL '24h', INTERVAL '100s', -1, INTERVAL '1h', '_timescaledb_internal', 'policy_telemetry', CURRENT_ROLE, true, false) +(1, 'Telemetry Reporter [1]', INTERVAL '24h', INTERVAL '100s', -1, INTERVAL '1h', '_timescaledb_internal', 'policy_telemetry', current_role::regrole, true, false) ON CONFLICT (id) DO NOTHING; diff --git a/src/bgw/job.c b/src/bgw/job.c index 23987085d..a960a8aa6 100644 --- a/src/bgw/job.c +++ b/src/bgw/job.c @@ -291,8 +291,7 @@ bgw_job_from_tupleinfo(TupleInfo *ti, size_t alloc_size) NameStr( *DatumGetName(values[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)]))); if (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_owner)]) - namestrcpy(&job->fd.owner, - NameStr(*DatumGetName(values[AttrNumberGetAttrOffset(Anum_bgw_job_owner)]))); + job->fd.owner = DatumGetObjectId(values[AttrNumberGetAttrOffset(Anum_bgw_job_owner)]); if (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)]) job->fd.scheduled = DatumGetBool(values[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)]); @@ -977,9 +976,7 @@ ts_bgw_job_check_max_retries(BgwJob *job) void ts_bgw_job_permission_check(BgwJob *job) { - Oid owner_oid = get_role_oid(NameStr(job->fd.owner), false); - - if (!has_privs_of_role(GetUserId(), owner_oid)) + if (!has_privs_of_role(GetUserId(), job->fd.owner)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("insufficient permissions to alter job %d", job->fd.id))); @@ -1329,7 +1326,7 @@ int ts_bgw_job_insert_relation(Name application_name, Interval *schedule_interval, Interval *max_runtime, int32 max_retries, Interval *retry_period, Name proc_schema, Name proc_name, Name check_schema, Name check_name, - Name owner, bool scheduled, bool fixed_schedule, int32 hypertable_id, + Oid owner, bool scheduled, bool fixed_schedule, int32 hypertable_id, Jsonb *config, TimestampTz initial_start, const char *timezone) { Catalog *catalog = ts_catalog_get(); @@ -1363,7 +1360,7 @@ ts_bgw_job_insert_relation(Name application_name, Interval *schedule_interval, else nulls[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] = true; - values[AttrNumberGetAttrOffset(Anum_bgw_job_owner)] = NameGetDatum(owner); + values[AttrNumberGetAttrOffset(Anum_bgw_job_owner)] = ObjectIdGetDatum(owner); values[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)] = BoolGetDatum(scheduled); values[AttrNumberGetAttrOffset(Anum_bgw_job_fixed_schedule)] = BoolGetDatum(fixed_schedule); /* initial_start must have a value if the schedule is fixed */ @@ -1407,6 +1404,13 @@ ts_bgw_job_insert_relation(Name application_name, Interval *schedule_interval, ts_catalog_insert_values(rel, desc, values, nulls); ts_catalog_restore_user(&sec_ctx); + /* This is where we would add a call to recordDependencyOnOwner, but it + * cannot support dependencies on anything but built-in classes since + * getObjectClass() have a lot of hard-coded checks in place. + * + * Instead we have a check in process_utility.c that prevents dropping the + * user if there is a dependent job. */ + table_close(rel, NoLock); return values[AttrNumberGetAttrOffset(Anum_bgw_job_id)]; } diff --git a/src/bgw/job.h b/src/bgw/job.h index 408100c3c..9fd3137bb 100644 --- a/src/bgw/job.h +++ b/src/bgw/job.h @@ -45,7 +45,7 @@ extern TSDLLEXPORT bool ts_bgw_job_update_by_id(int32 job_id, BgwJob *job); extern TSDLLEXPORT int32 ts_bgw_job_insert_relation( Name application_name, Interval *schedule_interval, Interval *max_runtime, int32 max_retries, Interval *retry_period, Name proc_schema, Name proc_name, Name check_schema, Name check_name, - Name owner, bool scheduled, bool fixed_schedule, int32 hypertable_id, Jsonb *config, + Oid owner, bool scheduled, bool fixed_schedule, int32 hypertable_id, Jsonb *config, TimestampTz initial_start, const char *timezone); extern TSDLLEXPORT void ts_bgw_job_permission_check(BgwJob *job); diff --git a/src/bgw/scheduler.c b/src/bgw/scheduler.c index 1f855701f..3d9e7fd3a 100644 --- a/src/bgw/scheduler.c +++ b/src/bgw/scheduler.c @@ -251,7 +251,6 @@ scheduled_bgw_job_transition_state_to(ScheduledBgwJob *sjob, JobState new_state) #endif BgwJobStat *job_stat; - Oid owner_uid; switch (new_state) { @@ -314,7 +313,6 @@ scheduled_bgw_job_transition_state_to(ScheduledBgwJob *sjob, JobState new_state) else sjob->timeout_at = DT_NOEND; - owner_uid = get_role_oid(NameStr(sjob->job.fd.owner), false); CommitTransactionCommand(); MemoryContextSwitchTo(scratch_mctx); @@ -323,7 +321,7 @@ scheduled_bgw_job_transition_state_to(ScheduledBgwJob *sjob, JobState new_state) sjob->job.fd.id, NameStr(sjob->job.fd.application_name)); - sjob->handle = ts_bgw_job_start(&sjob->job, owner_uid); + sjob->handle = ts_bgw_job_start(&sjob->job, sjob->job.fd.owner); if (sjob->handle == NULL) { elog(WARNING, diff --git a/src/process_utility.c b/src/process_utility.c index 919ab92af..4d979ee2b 100644 --- a/src/process_utility.c +++ b/src/process_utility.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,7 @@ #include "compression_with_clause.h" #include "partitioning.h" #include "debug_point.h" +#include "debug_assert.h" #ifdef USE_TELEMETRY #include "telemetry/functions.h" @@ -1698,6 +1700,62 @@ process_drop_continuous_aggregates(ProcessUtilityArgs *args, DropStmt *stmt) errhint("Drop continuous aggregates and other objects in separate statements."))); } +static bool +fetch_role_info(RoleSpec *rolespec, Oid *roleid) +{ + /* Special role specifiers should not be present when dropping a role, + * but if they are, we just ignore them */ + if (rolespec->roletype != ROLESPEC_CSTRING) + return false; + + /* Fetch the heap tuple from system table. If heaptuple is not valid it + * means we did not find a role. We ignore it since the real execution + * will handle this. */ + HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(rolespec->rolename)); + if (!HeapTupleIsValid(tuple)) + return false; + + Form_pg_authid roleform = (Form_pg_authid) GETSTRUCT(tuple); + *roleid = roleform->oid; + ReleaseSysCache(tuple); + return true; +} + +static DDLResult +process_drop_role(ProcessUtilityArgs *args) +{ + DropRoleStmt *stmt = (DropRoleStmt *) args->parsetree; + ListCell *cell; + foreach (cell, stmt->roles) + { + RoleSpec *rolespec = lfirst(cell); + Oid roleid; + + if (!fetch_role_info(rolespec, &roleid)) + continue; + + ScanIterator iterator = + ts_scan_iterator_create(BGW_JOB, AccessShareLock, CurrentMemoryContext); + ts_scanner_foreach(&iterator) + { + bool isnull; + TupleInfo *ti = ts_scan_iterator_tuple_info(&iterator); + Datum value = slot_getattr(ti->slot, Anum_bgw_job_owner, &isnull); + if (!isnull && DatumGetObjectId(value) == roleid) + { + Datum value = slot_getattr(ti->slot, Anum_bgw_job_id, &isnull); + Ensure(!isnull, "job id was null"); + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("role \"%s\" cannot be dropped because some objects depend on it", + rolespec->rolename), + errdetail("owner of job %d", DatumGetInt32(value)))); + } + } + } + return DDL_CONTINUE; +} + static DDLResult process_drop_start(ProcessUtilityArgs *args) { @@ -4098,6 +4156,9 @@ process_ddl_command_start(ProcessUtilityArgs *args) */ handler = process_drop_start; break; + case T_DropRoleStmt: + handler = process_drop_role; + break; case T_DropTableSpaceStmt: handler = process_drop_tablespace; break; diff --git a/src/ts_catalog/catalog.h b/src/ts_catalog/catalog.h index 17bfd3cab..327352cb5 100644 --- a/src/ts_catalog/catalog.h +++ b/src/ts_catalog/catalog.h @@ -742,7 +742,7 @@ typedef struct FormData_bgw_job Interval retry_period; NameData proc_schema; NameData proc_name; - NameData owner; + Oid owner; bool scheduled; bool fixed_schedule; TimestampTz initial_start; diff --git a/test/expected/telemetry.out b/test/expected/telemetry.out index 0da208acc..91afcce26 100644 --- a/test/expected/telemetry.out +++ b/test/expected/telemetry.out @@ -557,16 +557,16 @@ EXECUTE record_from_prepared; DEALLOCATE record_from_prepared; SELECT get_telemetry_report()->'functions_used'; - ?column? ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"pg_catalog.count()": 1, "pg_catalog.sum(bigint)": 4, "pg_catalog.abs(integer)": 1, "pg_catalog.max(integer)": 2, "pg_catalog.min(integer)": 1, "pg_catalog.sum(integer)": 1, "pg_catalog.int8(numeric)": 4, "pg_catalog.sum(interval)": 2, "pg_catalog.current_database()": 1, "public.get_telemetry_report()": 1, "pg_catalog.text(pg_catalog.name)": 1, "pg_catalog.int4eq(integer,integer)": 3, "pg_catalog.int4mi(integer,integer)": 11, "pg_catalog.int4pl(integer,integer)": 3, "pg_catalog.concat(pg_catalog.\"any\")": 3, "pg_catalog.pg_get_userbyid(pg_catalog.oid)": 1, "pg_catalog.nameeq(pg_catalog.name,pg_catalog.name)": 2, "pg_catalog.texteq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.nameregexeq(pg_catalog.name,pg_catalog.text)": 1, "pg_catalog.textregexeq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.jsonb_object_agg(pg_catalog.\"any\",pg_catalog.\"any\")": 1, "pg_catalog.jsonb_object_field(pg_catalog.jsonb,pg_catalog.text)": 1, "pg_catalog.jsonb_object_field_text(pg_catalog.jsonb,pg_catalog.text)": 15, "pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.name,pg_catalog.text)": 2} + ?column? +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"pg_catalog.count()": 1, "pg_catalog.sum(bigint)": 4, "pg_catalog.abs(integer)": 1, "pg_catalog.max(integer)": 2, "pg_catalog.min(integer)": 1, "pg_catalog.sum(integer)": 1, "pg_catalog.int8(numeric)": 4, "pg_catalog.sum(interval)": 2, "pg_catalog.current_database()": 1, "public.get_telemetry_report()": 1, "pg_catalog.text(pg_catalog.name)": 1, "pg_catalog.int4eq(integer,integer)": 3, "pg_catalog.int4mi(integer,integer)": 11, "pg_catalog.int4pl(integer,integer)": 3, "pg_catalog.concat(pg_catalog.\"any\")": 3, "pg_catalog.pg_get_userbyid(pg_catalog.oid)": 1, "pg_catalog.nameeq(pg_catalog.name,pg_catalog.name)": 2, "pg_catalog.texteq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.nameregexeq(pg_catalog.name,pg_catalog.text)": 1, "pg_catalog.textregexeq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.jsonb_object_agg(pg_catalog.\"any\",pg_catalog.\"any\")": 1, "pg_catalog.jsonb_object_field(pg_catalog.jsonb,pg_catalog.text)": 1, "pg_catalog.jsonb_object_field_text(pg_catalog.jsonb,pg_catalog.text)": 15, "pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.oid,pg_catalog.text)": 1, "pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.name,pg_catalog.text)": 1} (1 row) -- check the report again to see if resetting works SELECT get_telemetry_report()->'functions_used'; - ?column? ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"pg_catalog.count()": 1, "pg_catalog.sum(bigint)": 4, "pg_catalog.max(integer)": 2, "pg_catalog.int8(numeric)": 4, "pg_catalog.sum(interval)": 2, "pg_catalog.current_database()": 1, "public.get_telemetry_report()": 1, "pg_catalog.text(pg_catalog.name)": 1, "pg_catalog.int4eq(integer,integer)": 2, "pg_catalog.concat(pg_catalog.\"any\")": 3, "pg_catalog.pg_get_userbyid(pg_catalog.oid)": 1, "pg_catalog.nameeq(pg_catalog.name,pg_catalog.name)": 2, "pg_catalog.texteq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.nameregexeq(pg_catalog.name,pg_catalog.text)": 1, "pg_catalog.textregexeq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.jsonb_object_agg(pg_catalog.\"any\",pg_catalog.\"any\")": 1, "pg_catalog.jsonb_object_field(pg_catalog.jsonb,pg_catalog.text)": 1, "pg_catalog.jsonb_object_field_text(pg_catalog.jsonb,pg_catalog.text)": 15, "pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.name,pg_catalog.text)": 2} + ?column? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"pg_catalog.count()": 1, "pg_catalog.sum(bigint)": 4, "pg_catalog.max(integer)": 2, "pg_catalog.int8(numeric)": 4, "pg_catalog.sum(interval)": 2, "pg_catalog.current_database()": 1, "public.get_telemetry_report()": 1, "pg_catalog.text(pg_catalog.name)": 1, "pg_catalog.int4eq(integer,integer)": 2, "pg_catalog.concat(pg_catalog.\"any\")": 3, "pg_catalog.pg_get_userbyid(pg_catalog.oid)": 1, "pg_catalog.nameeq(pg_catalog.name,pg_catalog.name)": 2, "pg_catalog.texteq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.nameregexeq(pg_catalog.name,pg_catalog.text)": 1, "pg_catalog.textregexeq(pg_catalog.text,pg_catalog.text)": 1, "pg_catalog.jsonb_object_agg(pg_catalog.\"any\",pg_catalog.\"any\")": 1, "pg_catalog.jsonb_object_field(pg_catalog.jsonb,pg_catalog.text)": 1, "pg_catalog.jsonb_object_field_text(pg_catalog.jsonb,pg_catalog.text)": 15, "pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.oid,pg_catalog.text)": 1, "pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.name,pg_catalog.text)": 1} (1 row) \c :TEST_DBNAME :ROLE_SUPERUSER diff --git a/tsl/src/bgw_policy/compression_api.c b/tsl/src/bgw_policy/compression_api.c index 37f2ef7e2..5aa18e7fb 100644 --- a/tsl/src/bgw_policy/compression_api.c +++ b/tsl/src/bgw_policy/compression_api.c @@ -320,7 +320,7 @@ policy_compression_add_internal(Oid user_rel_oid, Datum compress_after_datum, &proc_name, &check_schema, &check_name, - &owner, + owner_id, true, fixed_schedule, hypertable->fd.id, diff --git a/tsl/src/bgw_policy/continuous_aggregate_api.c b/tsl/src/bgw_policy/continuous_aggregate_api.c index 0bbf7c1e0..cfab7db22 100644 --- a/tsl/src/bgw_policy/continuous_aggregate_api.c +++ b/tsl/src/bgw_policy/continuous_aggregate_api.c @@ -633,7 +633,7 @@ policy_refresh_cagg_add_internal(Oid cagg_oid, Oid start_offset_type, NullableDa &proc_name, &check_schema, &check_name, - &owner, + owner_id, true, fixed_schedule, cagg->data.mat_hypertable_id, diff --git a/tsl/src/bgw_policy/job_api.c b/tsl/src/bgw_policy/job_api.c index 88d9c570f..422840a60 100644 --- a/tsl/src/bgw_policy/job_api.c +++ b/tsl/src/bgw_policy/job_api.c @@ -80,7 +80,6 @@ job_add(PG_FUNCTION_ARGS) NameData application_name; NameData proc_name; NameData proc_schema; - NameData owner_name; NameData check_name = { .data = { 0 } }; NameData check_schema = { .data = { 0 } }; Interval max_runtime = { .time = DEFAULT_MAX_RUNTIME }; @@ -167,7 +166,6 @@ job_add(PG_FUNCTION_ARGS) namestrcpy(&application_name, "User-Defined Action"); namestrcpy(&proc_schema, get_namespace_name(get_func_namespace(proc))); namestrcpy(&proc_name, func_name); - namestrcpy(&owner_name, GetUserNameFromId(owner, false)); /* The check exists but may not have the expected signature: (config jsonb) */ if (OidIsValid(check)) @@ -184,7 +182,7 @@ job_add(PG_FUNCTION_ARGS) &proc_name, &check_schema, &check_name, - &owner_name, + owner, scheduled, fixed_schedule, 0, @@ -240,18 +238,16 @@ job_delete(PG_FUNCTION_ARGS) { int32 job_id = PG_GETARG_INT32(0); BgwJob *job; - Oid owner; TS_PREVENT_FUNC_IF_READ_ONLY(); job = find_job(job_id, PG_ARGISNULL(0), false); - owner = get_role_oid(NameStr(job->fd.owner), false); - if (!has_privs_of_role(GetUserId(), owner)) + if (!has_privs_of_role(GetUserId(), job->fd.owner)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("insufficient permissions to delete job for user \"%s\"", - NameStr(job->fd.owner)))); + GetUserNameFromId(job->fd.owner, false)))); ts_bgw_job_delete_by_id(job_id); diff --git a/tsl/src/bgw_policy/reorder_api.c b/tsl/src/bgw_policy/reorder_api.c index 272927593..66410759a 100644 --- a/tsl/src/bgw_policy/reorder_api.c +++ b/tsl/src/bgw_policy/reorder_api.c @@ -281,7 +281,7 @@ policy_reorder_add(PG_FUNCTION_ARGS) &proc_name, &check_schema, &check_name, - &owner, + owner_id, true, fixed_schedule, hypertable_id, diff --git a/tsl/src/bgw_policy/retention_api.c b/tsl/src/bgw_policy/retention_api.c index f7bd2c5d2..51ae4e9da 100644 --- a/tsl/src/bgw_policy/retention_api.c +++ b/tsl/src/bgw_policy/retention_api.c @@ -272,12 +272,11 @@ policy_retention_add_internal(Oid ht_oid, Oid window_type, Datum window_datum, /* Next, insert a new job into jobs table */ namestrcpy(&application_name, "Retention Policy"); - NameData proc_name, proc_schema, check_schema, check_name, owner; + NameData proc_name, proc_schema, check_schema, check_name; namestrcpy(&proc_name, POLICY_RETENTION_PROC_NAME); namestrcpy(&proc_schema, INTERNAL_SCHEMA_NAME); namestrcpy(&check_name, POLICY_RETENTION_CHECK_NAME); namestrcpy(&check_schema, INTERNAL_SCHEMA_NAME); - namestrcpy(&owner, GetUserNameFromId(owner_id, false)); job_id = ts_bgw_job_insert_relation(&application_name, &default_schedule_interval, @@ -288,7 +287,7 @@ policy_retention_add_internal(Oid ht_oid, Oid window_type, Datum window_datum, &proc_name, &check_schema, &check_name, - &owner, + owner_id, true, fixed_schedule, hypertable->fd.id, diff --git a/tsl/test/expected/bgw_custom.out b/tsl/test/expected/bgw_custom.out index 0f2de308c..84f0fbea4 100644 --- a/tsl/test/expected/bgw_custom.out +++ b/tsl/test/expected/bgw_custom.out @@ -864,6 +864,8 @@ ERROR: permission denied for function "test_config_check_func_privileges" \c :TEST_DBNAME :ROLE_SUPERUSER DROP SCHEMA test_schema CASCADE; NOTICE: drop cascades to function test_schema.test_config_check_func_privileges(jsonb) +-- Delete all jobs with that owner before we can drop the user. +DELETE FROM _timescaledb_config.bgw_job WHERE owner = 'user_noexec'::regrole; DROP ROLE user_noexec; -- test with aggregate check proc create function jsonb_add (j1 jsonb, j2 jsonb) returns jsonb diff --git a/tsl/test/expected/bgw_db_scheduler.out b/tsl/test/expected/bgw_db_scheduler.out index 7f410d881..74b925741 100644 --- a/tsl/test/expected/bgw_db_scheduler.out +++ b/tsl/test/expected/bgw_db_scheduler.out @@ -16,7 +16,16 @@ CREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID AS :MODULE_PATHN CREATE OR REPLACE FUNCTION ts_bgw_test_job_sleep(job_id INT, config JSONB) RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -CREATE OR REPLACE FUNCTION insert_job(application_name NAME,job_type NAME, schedule_interval INTERVAL, max_runtime INTERVAL, retry_period INTERVAL, owner NAME DEFAULT CURRENT_ROLE, scheduled BOOL DEFAULT true, fixed_schedule BOOL DEFAULT false) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS +CREATE OR REPLACE FUNCTION insert_job( + application_name NAME, + job_type NAME, + schedule_interval INTERVAL, + max_runtime INTERVAL, + retry_period INTERVAL, + owner regrole DEFAULT CURRENT_ROLE::regrole, + scheduled BOOL DEFAULT true, + fixed_schedule BOOL DEFAULT false +) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS $$ INSERT INTO _timescaledb_config.bgw_job(application_name,schedule_interval,max_runtime,max_retries, retry_period,proc_name,proc_schema,owner,scheduled,fixed_schedule) @@ -1603,6 +1612,35 @@ consecutive_crashes | 0 flags | 0 \x off +-- Test renaming a user and see that the owner of the job changes. +\c :TEST_DBNAME :ROLE_SUPERUSER +CREATE USER another_user; +SET ROLE another_user; +SELECT insert_job('another_one', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s') AS job_id \gset +SELECT proc_name, owner FROM _timescaledb_config.bgw_job WHERE id = :job_id; + proc_name | owner +----------------+-------------- + bgw_test_job_1 | another_user +(1 row) + +RESET ROLE; +ALTER USER another_user RENAME TO renamed_user; +SELECT proc_name, owner FROM _timescaledb_config.bgw_job WHERE id = :job_id; + proc_name | owner +----------------+-------------- + bgw_test_job_1 | renamed_user +(1 row) + +-- This should fail since the job is dependent on the owner +\set VERBOSITY default +\set ON_ERROR_STOP 0 +DROP USER renamed_user; +ERROR: role "renamed_user" cannot be dropped because some objects depend on it +DETAIL: owner of job 1026 +\set ON_ERROR_STOP 1 +DELETE FROM _timescaledb_config.bgw_job WHERE id = :job_id; +-- This should succeed +DROP USER renamed_user; -- clean up jobs SELECT _timescaledb_internal.stop_background_workers(); stop_background_workers diff --git a/tsl/test/expected/bgw_db_scheduler_fixed.out b/tsl/test/expected/bgw_db_scheduler_fixed.out index 80f9fff21..307ddab66 100644 --- a/tsl/test/expected/bgw_db_scheduler_fixed.out +++ b/tsl/test/expected/bgw_db_scheduler_fixed.out @@ -18,7 +18,16 @@ CREATE OR REPLACE FUNCTION ts_bgw_test_job_sleep(job_id INT, config JSONB) RETUR CREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -- we use insert_job instead of add_job because we want to be able to set and use max_retries, max_runtime, retry_period which are not part of the add_job api -CREATE OR REPLACE FUNCTION insert_job(application_name NAME,job_type NAME, schedule_interval INTERVAL, max_runtime INTERVAL, retry_period INTERVAL, owner NAME DEFAULT CURRENT_ROLE, scheduled BOOL DEFAULT true, fixed_schedule BOOL DEFAULT true) +CREATE OR REPLACE FUNCTION insert_job( + application_name NAME, + job_type NAME, + schedule_interval INTERVAL, + max_runtime INTERVAL, + retry_period INTERVAL, + owner regrole DEFAULT current_role::regrole, + scheduled BOOL DEFAULT true, + fixed_schedule BOOL DEFAULT true +) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS $$ INSERT INTO _timescaledb_config.bgw_job(application_name,schedule_interval,max_runtime,max_retries, diff --git a/tsl/test/sql/bgw_custom.sql b/tsl/test/sql/bgw_custom.sql index 4e8b3f383..c8cd522ff 100644 --- a/tsl/test/sql/bgw_custom.sql +++ b/tsl/test/sql/bgw_custom.sql @@ -530,6 +530,9 @@ select * from alter_job(:job_id_owner, check_config => 'test_schema.test_config_ \c :TEST_DBNAME :ROLE_SUPERUSER DROP SCHEMA test_schema CASCADE; + +-- Delete all jobs with that owner before we can drop the user. +DELETE FROM _timescaledb_config.bgw_job WHERE owner = 'user_noexec'::regrole; DROP ROLE user_noexec; -- test with aggregate check proc diff --git a/tsl/test/sql/bgw_db_scheduler.sql b/tsl/test/sql/bgw_db_scheduler.sql index 030cef85a..c621b95c2 100644 --- a/tsl/test/sql/bgw_db_scheduler.sql +++ b/tsl/test/sql/bgw_db_scheduler.sql @@ -22,7 +22,16 @@ CREATE OR REPLACE FUNCTION ts_bgw_test_job_sleep(job_id INT, config JSONB) RETUR CREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -CREATE OR REPLACE FUNCTION insert_job(application_name NAME,job_type NAME, schedule_interval INTERVAL, max_runtime INTERVAL, retry_period INTERVAL, owner NAME DEFAULT CURRENT_ROLE, scheduled BOOL DEFAULT true, fixed_schedule BOOL DEFAULT false) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS +CREATE OR REPLACE FUNCTION insert_job( + application_name NAME, + job_type NAME, + schedule_interval INTERVAL, + max_runtime INTERVAL, + retry_period INTERVAL, + owner regrole DEFAULT CURRENT_ROLE::regrole, + scheduled BOOL DEFAULT true, + fixed_schedule BOOL DEFAULT false +) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS $$ INSERT INTO _timescaledb_config.bgw_job(application_name,schedule_interval,max_runtime,max_retries, retry_period,proc_name,proc_schema,owner,scheduled,fixed_schedule) @@ -656,6 +665,32 @@ SELECT * FROM sorted_bgw_log; \x on SELECT * FROM _timescaledb_internal.bgw_job_stat; \x off + +-- Test renaming a user and see that the owner of the job changes. +\c :TEST_DBNAME :ROLE_SUPERUSER +CREATE USER another_user; + +SET ROLE another_user; +SELECT insert_job('another_one', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s') AS job_id \gset + +SELECT proc_name, owner FROM _timescaledb_config.bgw_job WHERE id = :job_id; + +RESET ROLE; +ALTER USER another_user RENAME TO renamed_user; + +SELECT proc_name, owner FROM _timescaledb_config.bgw_job WHERE id = :job_id; + +-- This should fail since the job is dependent on the owner +\set VERBOSITY default +\set ON_ERROR_STOP 0 +DROP USER renamed_user; +\set ON_ERROR_STOP 1 + +DELETE FROM _timescaledb_config.bgw_job WHERE id = :job_id; + +-- This should succeed +DROP USER renamed_user; + -- clean up jobs SELECT _timescaledb_internal.stop_background_workers(); diff --git a/tsl/test/sql/bgw_db_scheduler_fixed.sql b/tsl/test/sql/bgw_db_scheduler_fixed.sql index 6cd64c19a..0f664226b 100644 --- a/tsl/test/sql/bgw_db_scheduler_fixed.sql +++ b/tsl/test/sql/bgw_db_scheduler_fixed.sql @@ -24,7 +24,16 @@ CREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BO AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -- we use insert_job instead of add_job because we want to be able to set and use max_retries, max_runtime, retry_period which are not part of the add_job api -CREATE OR REPLACE FUNCTION insert_job(application_name NAME,job_type NAME, schedule_interval INTERVAL, max_runtime INTERVAL, retry_period INTERVAL, owner NAME DEFAULT CURRENT_ROLE, scheduled BOOL DEFAULT true, fixed_schedule BOOL DEFAULT true) +CREATE OR REPLACE FUNCTION insert_job( + application_name NAME, + job_type NAME, + schedule_interval INTERVAL, + max_runtime INTERVAL, + retry_period INTERVAL, + owner regrole DEFAULT current_role::regrole, + scheduled BOOL DEFAULT true, + fixed_schedule BOOL DEFAULT true +) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS $$ INSERT INTO _timescaledb_config.bgw_job(application_name,schedule_interval,max_runtime,max_retries,