diff --git a/docs/graphql/manual/event-triggers/payload.rst b/docs/graphql/manual/event-triggers/payload.rst index 1baebca1c3c..b1db137a0d1 100644 --- a/docs/graphql/manual/event-triggers/payload.rst +++ b/docs/graphql/manual/event-triggers/payload.rst @@ -23,10 +23,11 @@ JSON payload { "event": { + "session_variables": , "op": "", "data": { - "old": , - "new": + "old": , + "new": } }, "created_at": "", @@ -48,6 +49,9 @@ JSON payload * - Key - Type - Description + * - session-variables + - Object_ or NULL + - Key-value pairs of session variables (i.e. "x-hasura-\*" variables) and their values. NULL if no session variables found. * - op-name - OpName_ - Name of the operation. Can only be "INSERT", "UPDATE" or "DELETE" @@ -104,6 +108,11 @@ JSON payload "name": "users" }, "event": { + "session_variables": { + "x-hasura-role": "admin", + "x-hasura-allowed-roles": "['user', 'boo', 'admin']", + "x-hasura-user-id": "1" + }, "op": "INSERT", "data": { "old": null, diff --git a/server/src-lib/Hasura/Events/Lib.hs b/server/src-lib/Hasura/Events/Lib.hs index 1ab6a780697..6313c317f08 100644 --- a/server/src-lib/Hasura/Events/Lib.hs +++ b/server/src-lib/Hasura/Events/Lib.hs @@ -88,13 +88,13 @@ instance ToJSON Event where $(deriveFromJSON (aesonDrop 1 snakeCase){omitNothingFields=True} ''Event) -data Request - = Request +data WebhookRequest + = WebhookRequest { _rqPayload :: Value , _rqHeaders :: Maybe [HeaderConf] , _rqVersion :: T.Text } -$(deriveToJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''Request) +$(deriveToJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''WebhookRequest) data WebhookResponse = WebhookResponse @@ -117,7 +117,7 @@ data Invocation = Invocation { iEventId :: EventId , iStatus :: Int - , iRequest :: Request + , iRequest :: WebhookRequest , iResponse :: Response } @@ -338,8 +338,8 @@ tryWebhook logenv pool e = do where decodeBS = TE.decodeUtf8With TE.lenientDecode - mkWebhookReq :: Value -> [HeaderConf] -> Request - mkWebhookReq payload headers = Request payload (mkMaybe headers) invocationVersion + mkWebhookReq :: Value -> [HeaderConf] -> WebhookRequest + mkWebhookReq payload headers = WebhookRequest payload (mkMaybe headers) invocationVersion mkResp :: Int -> TBS.TByteString -> [HeaderConf] -> Response mkResp status payload headers = diff --git a/server/src-rsr/trigger.sql.j2 b/server/src-rsr/trigger.sql.j2 index 2135499eeca..8fd23154e25 100644 --- a/server/src-rsr/trigger.sql.j2 +++ b/server/src-rsr/trigger.sql.j2 @@ -7,8 +7,20 @@ CREATE OR REPLACE function hdb_views.{{QUALIFIED_TRIGGER_NAME}}() RETURNS trigge _new record; _data json; payload json; + session_variables json; + server_version_num int; BEGIN id := gen_random_uuid(); + server_version_num := current_setting('server_version_num'); + IF server_version_num >= 90600 THEN + session_variables := current_setting('hasura.user', 't'); + ELSE + BEGIN + session_variables := current_setting('hasura.user'); + EXCEPTION WHEN OTHERS THEN + session_variables := NULL; + END; + END IF; IF TG_OP = 'UPDATE' THEN _old := {{OLD_ROW}}; _new := {{NEW_ROW}}; @@ -23,7 +35,8 @@ CREATE OR REPLACE function hdb_views.{{QUALIFIED_TRIGGER_NAME}}() RETURNS trigge ); payload := json_build_object( 'op', TG_OP, - 'data', _data + 'data', _data, + 'session_variables', session_variables )::text; IF (TG_OP <> 'UPDATE') OR (_old <> _new) THEN INSERT INTO diff --git a/server/tests-py/context.py b/server/tests-py/context.py index 309668d02ad..f91f3640912 100644 --- a/server/tests-py/context.py +++ b/server/tests-py/context.py @@ -137,8 +137,8 @@ class HGECtx: ) return resp.status_code, resp.json() - def v1q(self, q): - h = dict() + def v1q(self, q, headers = {}): + h = headers.copy() if self.hge_key is not None: h['X-Hasura-Access-Key'] = self.hge_key resp = self.http.post( diff --git a/server/tests-py/test_events.py b/server/tests-py/test_events.py index 302750a69c1..c08925b7ac5 100755 --- a/server/tests-py/test_events.py +++ b/server/tests-py/test_events.py @@ -21,7 +21,7 @@ def select_last_event_fromdb(hge_ctx): return st_code, resp -def insert(hge_ctx, table, row, returning=[]): +def insert(hge_ctx, table, row, returning=[], headers = {}): q = { "type": "insert", "args": { @@ -30,11 +30,11 @@ def insert(hge_ctx, table, row, returning=[]): "returning": returning } } - st_code, resp = hge_ctx.v1q(q) + st_code, resp = hge_ctx.v1q(q, headers = headers) return st_code, resp -def update(hge_ctx, table, where_exp, set_exp): +def update(hge_ctx, table, where_exp, set_exp, headers = {}): q = { "type": "update", "args": { @@ -43,11 +43,11 @@ def update(hge_ctx, table, where_exp, set_exp): "$set": set_exp } } - st_code, resp = hge_ctx.v1q(q) + st_code, resp = hge_ctx.v1q(q, headers = headers) return st_code, resp -def delete(hge_ctx, table, where_exp): +def delete(hge_ctx, table, where_exp, headers = {}): q = { "type": "delete", "args": { @@ -55,7 +55,7 @@ def delete(hge_ctx, table, where_exp): "where": where_exp } } - st_code, resp = hge_ctx.v1q(q) + st_code, resp = hge_ctx.v1q(q, headers = headers) return st_code, resp class TestCreateAndDelete(DefaultTestQueries): @@ -89,10 +89,9 @@ class TestCreateEvtQuery(object): "old": None, "new": init_row } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp - check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c2": "world"} @@ -102,7 +101,7 @@ class TestCreateEvtQuery(object): } st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_all", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "UPDATE", exp_ev_data) exp_ev_data = { "old": {"c1": 1, "c2": "world"}, @@ -110,7 +109,7 @@ class TestCreateEvtQuery(object): } st_code, resp = delete(hge_ctx, table, where_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_all", table, "DELETE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "DELETE", exp_ev_data) class TestRetryConf(object): @@ -132,7 +131,6 @@ class TestRetryConf(object): "old": None, "new": init_row } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp time.sleep(15) @@ -162,7 +160,7 @@ class TestEvtHeaders(object): headers = {"X-Header-From-Value": "MyValue", "X-Header-From-Env": "MyEnvValue"} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp - check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data, headers = headers) class TestUpdateEvtQuery(object): @@ -186,11 +184,10 @@ class TestUpdateEvtQuery(object): "old": None, "new": {"c1": 1, "c2": "hello"} } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_cols", table, "INSERT", exp_ev_data, headers, "/new") + check_event(hge_ctx, "t1_cols", table, "INSERT", exp_ev_data, webhook_path = "/new") where_exp = {"c1": 1} set_exp = {"c2": "world"} @@ -198,7 +195,7 @@ class TestUpdateEvtQuery(object): st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data, headers, "/new") + check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data, webhook_path = "/new") where_exp = {"c1": 1} set_exp = {"c1": 2} @@ -208,7 +205,7 @@ class TestUpdateEvtQuery(object): } st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data, headers, "/new") + check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data, webhook_path ="/new") where_exp = {"c1": 2} exp_ev_data = { @@ -217,7 +214,7 @@ class TestUpdateEvtQuery(object): } st_code, resp = delete(hge_ctx, table, where_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_cols", table, "DELETE", exp_ev_data, headers, "/new") + check_event(hge_ctx, "t1_cols", table, "DELETE", exp_ev_data, webhook_path = "/new") class TestDeleteEvtQuery(object): @@ -241,11 +238,10 @@ class TestDeleteEvtQuery(object): "old": None, "new": init_row } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c2": "world"} @@ -256,7 +252,7 @@ class TestDeleteEvtQuery(object): st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_all", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "UPDATE", exp_ev_data) exp_ev_data = { "old": {"c1": 1, "c2": "world"}, @@ -265,7 +261,7 @@ class TestDeleteEvtQuery(object): st_code, resp = delete(hge_ctx, table, where_exp) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_all", table, "DELETE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "DELETE", exp_ev_data) class TestEvtSelCols: @@ -287,10 +283,9 @@ class TestEvtSelCols: "old": None, "new": {"c1": 1, "c2": "hello"} } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp - check_event(hge_ctx, "t1_cols", table, "INSERT", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_cols", table, "INSERT", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c2": "world"} @@ -298,7 +293,7 @@ class TestEvtSelCols: st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c1": 2} @@ -308,7 +303,7 @@ class TestEvtSelCols: } st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_cols", table, "UPDATE", exp_ev_data) where_exp = {"c1": 2} exp_ev_data = { @@ -317,7 +312,7 @@ class TestEvtSelCols: } st_code, resp = delete(hge_ctx, table, where_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_cols", table, "DELETE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_cols", table, "DELETE", exp_ev_data) def test_selected_cols_dep(self, hge_ctx): st_code, resp = hge_ctx.v1q({ @@ -357,10 +352,9 @@ class TestEvtInsertOnly: "old": None, "new": init_row } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp - check_event(hge_ctx, "t1_insert", table, "INSERT", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_insert", table, "INSERT", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c2": "world"} @@ -371,7 +365,7 @@ class TestEvtInsertOnly: st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_insert", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_insert", table, "UPDATE", exp_ev_data) exp_ev_data = { "old": {"c1": 1, "c2": "world"}, @@ -380,7 +374,7 @@ class TestEvtInsertOnly: st_code, resp = delete(hge_ctx, table, where_exp) assert st_code == 200, resp with pytest.raises(queue.Empty): - check_event(hge_ctx, "t1_insert", table, "DELETE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_insert", table, "DELETE", exp_ev_data) class TestEvtSelPayload: @@ -402,10 +396,9 @@ class TestEvtSelPayload: "old": None, "new": {"c1": 1, "c2": "hello"} } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp - check_event(hge_ctx, "t1_payload", table, "INSERT", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_payload", table, "INSERT", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c2": "world"} @@ -415,7 +408,7 @@ class TestEvtSelPayload: } st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_payload", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_payload", table, "UPDATE", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c1": 2} @@ -425,7 +418,7 @@ class TestEvtSelPayload: } st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_payload", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_payload", table, "UPDATE", exp_ev_data) where_exp = {"c1": 2} exp_ev_data = { @@ -434,7 +427,7 @@ class TestEvtSelPayload: } st_code, resp = delete(hge_ctx, table, where_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_payload", table, "DELETE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_payload", table, "DELETE", exp_ev_data) def test_selected_payload_dep(self, hge_ctx): st_code, resp = hge_ctx.v1q({ @@ -474,10 +467,9 @@ class TestWebhookEnv(object): "old": None, "new": init_row } - headers = {} st_code, resp = insert(hge_ctx, table, init_row) assert st_code == 200, resp - check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data) where_exp = {"c1": 1} set_exp = {"c2": "world"} @@ -487,7 +479,7 @@ class TestWebhookEnv(object): } st_code, resp = update(hge_ctx, table, where_exp, set_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_all", table, "UPDATE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "UPDATE", exp_ev_data) exp_ev_data = { "old": {"c1": 1, "c2": "world"}, @@ -495,4 +487,48 @@ class TestWebhookEnv(object): } st_code, resp = delete(hge_ctx, table, where_exp) assert st_code == 200, resp - check_event(hge_ctx, "t1_all", table, "DELETE", exp_ev_data, headers, "/") + check_event(hge_ctx, "t1_all", table, "DELETE", exp_ev_data) + +class TestSessionVariables(object): + + @pytest.fixture(autouse=True) + def transact(self, request, hge_ctx): + print("In setup method") + st_code, resp = hge_ctx.v1q_f('queries/event_triggers/basic/setup.yaml') + assert st_code == 200, resp + yield + st_code, resp = hge_ctx.v1q_f('queries/event_triggers/basic/teardown.yaml') + assert st_code == 200, resp + + def test_basic(self, hge_ctx): + table = {"schema": "hge_tests", "name": "test_t1"} + + init_row = {"c1": 1, "c2": "hello"} + exp_ev_data = { + "old": None, + "new": init_row + } + session_variables = { 'x-hasura-role': 'admin', 'x-hasura-allowed-roles': "['admin','user']", 'x-hasura-user-id': '1'} + st_code, resp = insert(hge_ctx, table, init_row, headers = session_variables) + assert st_code == 200, resp + check_event(hge_ctx, "t1_all", table, "INSERT", exp_ev_data, session_variables = session_variables) + + where_exp = {"c1": 1} + set_exp = {"c2": "world"} + exp_ev_data = { + "old": init_row, + "new": {"c1": 1, "c2": "world"} + } + session_variables = { 'x-hasura-role': 'admin', 'x-hasura-random': 'some_random_info', 'X-Random-Header': 'not_session_variable'} + st_code, resp = update(hge_ctx, table, where_exp, set_exp, headers = session_variables) + assert st_code == 200, resp + session_variables.pop('X-Random-Header') + check_event(hge_ctx, "t1_all", table, "UPDATE", exp_ev_data, session_variables = session_variables) + + exp_ev_data = { + "old": {"c1": 1, "c2": "world"}, + "new": None + } + st_code, resp = delete(hge_ctx, table, where_exp) + assert st_code == 200, resp + check_event(hge_ctx, "t1_all", table, "DELETE", exp_ev_data) diff --git a/server/tests-py/validate.py b/server/tests-py/validate.py index 31d01286262..4a0042326e4 100644 --- a/server/tests-py/validate.py +++ b/server/tests-py/validate.py @@ -40,13 +40,18 @@ def validate_event_webhook(ev_webhook_path, webhook_path): assert ev_webhook_path == webhook_path -def check_event(hge_ctx, trig_name, table, operation, exp_ev_data, headers, webhook_path): +def check_event(hge_ctx, trig_name, table, operation, exp_ev_data, + headers = {}, + webhook_path = '/', + session_variables = {'x-hasura-role': 'admin'} +): ev_full = hge_ctx.get_event(3) validate_event_webhook(ev_full['path'], webhook_path) validate_event_headers(ev_full['headers'], headers) validate_event_payload(ev_full['body'], trig_name, table) ev = ev_full['body']['event'] assert ev['op'] == operation, ev + assert ev['session_variables'] == session_variables, ev assert ev['data'] == exp_ev_data, ev