Fix chunk creation with user that lacks TRIGGER permission

Previously, automatic chunk creation on INSERT failed due to lack of
permissions when the hypertable had triggers that needed to be
replicated to the new chunk. This happened because creating triggers
on tables requires TRIGGER permission, and the internal code used
CreateTrigger() that performs permissions checks. Thus, if the user
inserting tuples into the hypertable only had INSERT permissions, the
insert would fail whenever a new chunk was created.

This change fixes the issue by temporarily assuming the role of the
hypertable owner when executing CreateTrigger() on the new chunk.
This commit is contained in:
Erik Nordström 2018-05-02 11:37:46 +02:00 committed by Erik Nordström
parent 43f812c158
commit fe20e489a9
3 changed files with 72 additions and 17 deletions

View File

@ -1,10 +1,12 @@
#include <postgres.h>
#include <utils/rel.h>
#include <utils/lsyscache.h>
#include <utils/syscache.h>
#include <utils/builtins.h>
#include <tcop/tcopprot.h>
#include <commands/trigger.h>
#include <access/xact.h>
#include <miscadmin.h>
#include "trigger.h"
#include "compat.h"
@ -56,8 +58,16 @@ trigger_by_name(Oid relid, const char *trigname, bool missing_ok)
return trigger;
}
/* all creation of triggers on chunks should go through this. Strictly speaking,
* this deparsing is not necessary in all cases, but this keeps things consistent. */
/*
* Replicate a trigger on a chunk.
*
* Given a trigger OID (e.g., a Hypertable trigger), create the equivalent
* trigger on a chunk.
*
* Note: it is assumed that this function is called under a user that has
* permissions to modify the chunk since CreateTrigger() performs permissions
* checks.
*/
void
trigger_create_on_chunk(Oid trigger_oid, char *chunk_schema_name, char *chunk_table_name)
{
@ -118,7 +128,7 @@ for_each_trigger(Oid relid, trigger_handler on_trigger, void *arg)
Trigger *trigger = &trigdesc->triggers[i];
if (!on_trigger(trigger, arg))
return;
break;
}
}
@ -145,10 +155,44 @@ create_trigger_handler(Trigger *trigger, void *arg)
return true;
}
/*
* Create all hypertable triggers on a new chunk.
*
* Since chunk creation typically happens automatically on hypertable INSERT, we
* need to execute the trigger creation under the role of the hypertable owner.
* This is due to the use of CreateTrigger(), which does permissions checks. The
* user role inserting might have INSERT permissions, but not TRIGGER
* permissions (needed to create triggers on a table).
*
* We assume that the owner of the Hypertable is also the owner of the new
* chunk.
*/
void
trigger_create_all_on_chunk(Hypertable *ht, Chunk *chunk)
{
int sec_ctx;
Oid saved_uid;
HeapTuple tuple;
Form_pg_class form;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(ht->main_table_relid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", ht->main_table_relid);
form = (Form_pg_class) GETSTRUCT(tuple);
GetUserIdAndSecContext(&saved_uid, &sec_ctx);
if (saved_uid != form->relowner)
SetUserIdAndSecContext(form->relowner, sec_ctx | SECURITY_LOCAL_USERID_CHANGE);
for_each_trigger(ht->main_table_relid, create_trigger_handler, chunk);
if (saved_uid != form->relowner)
SetUserIdAndSecContext(saved_uid, sec_ctx);
ReleaseSysCache(tuple);
}
#if PG10

View File

@ -285,7 +285,7 @@ CREATE TABLE location (
CREATE OR REPLACE FUNCTION create_vehicle_trigger_fn()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$BODY$
BEGIN
BEGIN
INSERT INTO vehicles VALUES(NEW.vehicle_id, NULL, NULL) ON CONFLICT DO NOTHING;
RETURN NEW;
END
@ -298,14 +298,11 @@ BEGIN
BEGIN
INSERT INTO color VALUES(NEW.color_id, 'n/a');
EXCEPTION WHEN unique_violation THEN
-- Nothing to do, just continue
END;
-- Nothing to do, just continue
END;
RETURN NEW;
END
$BODY$;
CREATE TRIGGER create_vehicle_trigger
BEFORE INSERT OR UPDATE ON location
FOR EACH ROW EXECUTE PROCEDURE create_vehicle_trigger_fn();
CREATE TRIGGER create_color_trigger
BEFORE INSERT OR UPDATE ON location
FOR EACH ROW EXECUTE PROCEDURE create_color_trigger_fn();
@ -322,6 +319,14 @@ SELECT create_hypertable('color', 'color_id', chunk_time_interval=>10);
(1 row)
-- Test that we can create and use triggers with another user
GRANT TRIGGER, INSERT, SELECT, UPDATE ON location TO :ROLE_DEFAULT_PERM_USER_2;
GRANT SELECT, INSERT, UPDATE ON color TO :ROLE_DEFAULT_PERM_USER_2;
GRANT SELECT, INSERT, UPDATE ON vehicles TO :ROLE_DEFAULT_PERM_USER_2;
\c single :ROLE_DEFAULT_PERM_USER_2;
CREATE TRIGGER create_vehicle_trigger
BEFORE INSERT OR UPDATE ON location
FOR EACH ROW EXECUTE PROCEDURE create_vehicle_trigger_fn();
INSERT INTO location VALUES('2017-01-01 01:02:03', 1, 1, 40.7493226,-73.9771259);
INSERT INTO location VALUES('2017-01-01 01:02:04', 1, 20, 24.7493226,-73.9771259);
INSERT INTO location VALUES('2017-01-01 01:02:03', 23, 1, 40.7493226,-73.9771269);

View File

@ -222,7 +222,7 @@ CREATE TABLE location (
CREATE OR REPLACE FUNCTION create_vehicle_trigger_fn()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$BODY$
BEGIN
BEGIN
INSERT INTO vehicles VALUES(NEW.vehicle_id, NULL, NULL) ON CONFLICT DO NOTHING;
RETURN NEW;
END
@ -237,25 +237,31 @@ BEGIN
BEGIN
INSERT INTO color VALUES(NEW.color_id, 'n/a');
EXCEPTION WHEN unique_violation THEN
-- Nothing to do, just continue
END;
-- Nothing to do, just continue
END;
RETURN NEW;
END
$BODY$;
CREATE TRIGGER create_vehicle_trigger
BEFORE INSERT OR UPDATE ON location
FOR EACH ROW EXECUTE PROCEDURE create_vehicle_trigger_fn();
CREATE TRIGGER create_color_trigger
BEFORE INSERT OR UPDATE ON location
FOR EACH ROW EXECUTE PROCEDURE create_color_trigger_fn();
SELECT create_hypertable('location', 'time');
--make color also a hypertable
SELECT create_hypertable('color', 'color_id', chunk_time_interval=>10);
-- Test that we can create and use triggers with another user
GRANT TRIGGER, INSERT, SELECT, UPDATE ON location TO :ROLE_DEFAULT_PERM_USER_2;
GRANT SELECT, INSERT, UPDATE ON color TO :ROLE_DEFAULT_PERM_USER_2;
GRANT SELECT, INSERT, UPDATE ON vehicles TO :ROLE_DEFAULT_PERM_USER_2;
\c single :ROLE_DEFAULT_PERM_USER_2;
CREATE TRIGGER create_vehicle_trigger
BEFORE INSERT OR UPDATE ON location
FOR EACH ROW EXECUTE PROCEDURE create_vehicle_trigger_fn();
INSERT INTO location VALUES('2017-01-01 01:02:03', 1, 1, 40.7493226,-73.9771259);
INSERT INTO location VALUES('2017-01-01 01:02:04', 1, 20, 24.7493226,-73.9771259);
INSERT INTO location VALUES('2017-01-01 01:02:03', 23, 1, 40.7493226,-73.9771269);