From abefe2c6a2c890c1ee1549c26060a1487b546f60 Mon Sep 17 00:00:00 2001
From: Andrey Saranchin <Andrey22102001@gmail.com>
Date: Fri, 7 Jul 2023 15:48:15 +0300
Subject: [PATCH] session: move all session triggers to the trigger registry

The commit populates submodule session with an event for each public
trigger - they will be used for user-defined triggers. Old triggers are
not removed because they are used for internal purposes - audit from EE
uses them, for instance. Also, the commit introduces a test for all
triggers that use old API. It checks only session triggers now - all the
other triggers will be included there after they will be moved to the
trigger registry.

In order to have an opportunity to move user-defined triggers to trigger
registry, the commit introduces helper luaT_event_reset_trigger that
supports two different ways to set triggers - key-value API and positional
API. The first one is a new API that allows to set or delete trigger only
by name. The second API is implemented for the sake of backward
compatibility - it mimics behavior of function lbox_trigger_reset, which
was used to modify triggers, but sets the triggers to event from trigger
registry instead of separate trigger list. Also, positional API allows
to pass a name as the third argument - in this case the function will
set or delete trigger by passed name - the first argument is a new
trigger (or nil, if the function is called to delete a trigger), the
second argument will be ignored in this case.

The new helper supports not only functions - any callable object can be
used as a trigger.

Part of #6484
Part of #8657

NO_CHANGELOG=later
NO_DOC=later
---
 src/box/authentication.c                   |   6 +-
 src/box/error.cc                           |  44 ++++-
 src/box/error.h                            |  22 +++
 src/box/iproto.cc                          |  10 +-
 src/box/lua/session.c                      |  44 +----
 src/box/lua/trigger.c                      | 147 +++++++++++++++
 src/box/lua/trigger.h                      |  43 +++++
 src/box/schema.h                           |  17 --
 src/box/session.c                          |  73 ++++++-
 src/box/session.h                          |   4 +
 test/box-luatest/triggers_old_api_test.lua | 209 +++++++++++++++++++++
 test/unit/luaT_tuple_new.c                 |   2 +
 test/unit/tuple_format.c                   |   3 +
 13 files changed, 551 insertions(+), 73 deletions(-)
 create mode 100644 test/box-luatest/triggers_old_api_test.lua

diff --git a/src/box/authentication.c b/src/box/authentication.c
index d9b2698552..dc4c6d45df 100644
--- a/src/box/authentication.c
+++ b/src/box/authentication.c
@@ -17,6 +17,7 @@
 #include "diag.h"
 #include "errcode.h"
 #include "error.h"
+#include "event.h"
 #include "fiber.h"
 #include "iostream.h"
 #include "msgpuck.h"
@@ -121,9 +122,8 @@ authenticate(const char *user_name, uint32_t user_name_len,
 	if (access_check_session(user) != 0)
 		return -1;
 ok:
-	/* check and run auth triggers on success */
-	if (! rlist_empty(&session_on_auth) &&
-	    session_run_on_auth_triggers(&auth_res) != 0)
+	/* run auth triggers on success */
+	if (session_run_on_auth_triggers(&auth_res) != 0)
 		return -1;
 	credentials_reset(&current_session()->credentials, user);
 	return 0;
diff --git a/src/box/error.cc b/src/box/error.cc
index e0b916454b..0dc3d26244 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -31,7 +31,9 @@
 #include "error.h"
 #include <stdio.h>
 
+#include "event.h"
 #include "fiber.h"
+#include "func_adapter.h"
 #include "rmean.h"
 #include "trigger.h"
 #include "vclock/vclock.h"
@@ -266,6 +268,40 @@ BuildXlogGapError(const char *file, unsigned line,
 }
 
 struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied);
+struct event *on_access_denied_event;
+
+/**
+ * Runs on access denied triggers. Does not run triggers from the event if it
+ * is not initialized.
+ */
+static int
+run_on_access_denied_triggers(const char *access_type, const char *object_type,
+			      const char *object_name)
+{
+	struct on_access_denied_ctx trigger_ctx =
+		{access_type, object_type, object_name};
+	if (trigger_run(&on_access_denied, &trigger_ctx) != 0)
+		return -1;
+
+	if (on_access_denied_event == NULL)
+		return 0;
+	const char *name = NULL;
+	struct func_adapter *trigger = NULL;
+	struct func_adapter_ctx ctx;
+	struct event_trigger_iterator it;
+	int rc = 0;
+	event_trigger_iterator_create(&it, on_access_denied_event);
+	while (rc == 0 && event_trigger_iterator_next(&it, &trigger, &name)) {
+		func_adapter_begin(trigger, &ctx);
+		func_adapter_push_str0(trigger, &ctx, access_type);
+		func_adapter_push_str0(trigger, &ctx, object_type);
+		func_adapter_push_str0(trigger, &ctx, object_name);
+		rc = func_adapter_call(trigger, &ctx);
+		func_adapter_end(trigger, &ctx);
+	}
+	event_trigger_iterator_destroy(&it);
+	return rc;
+}
 
 const struct type_info type_AccessDeniedError =
 	make_type("AccessDeniedError", &type_ClientError);
@@ -275,19 +311,19 @@ AccessDeniedError::AccessDeniedError(const char *file, unsigned int line,
 				     const char *object_type,
 				     const char *object_name,
 				     const char *user_name,
-				     bool run_trigers)
+				     bool run_triggers)
 	:ClientError(&type_AccessDeniedError, file, line, ER_ACCESS_DENIED)
 {
 	error_format_msg(this, tnt_errcode_desc(code),
 			 access_type, object_type, object_name, user_name);
 
-	struct on_access_denied_ctx ctx = {access_type, object_type, object_name};
 	/*
 	 * Don't run the triggers when create after marshaling
 	 * through network.
 	 */
-	if (run_trigers)
-		trigger_run(&on_access_denied, (void *) &ctx);
+	if (run_triggers)
+		run_on_access_denied_triggers(access_type, object_type,
+					      object_name);
 	error_set_str(this, "object_type", object_type);
 	error_set_str(this, "object_name", object_name);
 	error_set_str(this, "access_type", access_type);
diff --git a/src/box/error.h b/src/box/error.h
index 3cc08edf89..3033e3846f 100644
--- a/src/box/error.h
+++ b/src/box/error.h
@@ -184,6 +184,28 @@ extern const struct type_info type_XlogGapError;
 extern const struct type_info type_AccessDeniedError;
 extern const struct type_info type_CustomError;
 
+/**
+ * Internal triggers fired after access denied error is created.
+ */
+extern struct rlist on_access_denied;
+
+/**
+ * User-definded triggers fired after access denied error is created.
+ */
+extern struct event *on_access_denied_event;
+
+/**
+ * Context passed to on_access_denied trigger.
+ */
+struct on_access_denied_ctx {
+	/** Type of declined access */
+	const char *access_type;
+	/** Type of object the required access was denied to */
+	const char *object_type;
+	/** Name of object the required access was denied to */
+	const char *object_name;
+};
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #include "exception.h"
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index c724c161e5..fddd5b6d0a 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -40,6 +40,7 @@
 #include <base64.h>
 
 #include "version.h"
+#include "event.h"
 #include "fiber.h"
 #include "fiber_cond.h"
 #include "cbus.h"
@@ -1777,10 +1778,8 @@ tx_process_disconnect(struct cmsg *m)
 		 * closed, its push() method is replaced with a stub.
 		 */
 		con->tx.is_push_pending = false;
-		if (! rlist_empty(&session_on_disconnect)) {
-			tx_fiber_init(con->session, 0);
-			session_run_on_disconnect_triggers(con->session);
-		}
+		tx_fiber_init(con->session, 0);
+		session_run_on_disconnect_triggers(con->session);
 	}
 }
 
@@ -2726,8 +2725,7 @@ tx_process_connect(struct cmsg *m)
 	greeting_encode(greeting, tarantool_version_id(), &uuid,
 			con->salt, IPROTO_SALT_SIZE);
 	xobuf_dup(out, greeting, IPROTO_GREETING_SIZE);
-	if (!rlist_empty(&session_on_connect) &&
-	    session_run_on_connect_triggers(con->session) != 0)
+	if (session_run_on_connect_triggers(con->session) != 0)
 		goto error;
 	iproto_wpos_create(&msg->wpos, out);
 	return;
diff --git a/src/box/lua/session.c b/src/box/lua/session.c
index 811cc958c6..16f9e938f6 100644
--- a/src/box/lua/session.c
+++ b/src/box/lua/session.c
@@ -36,8 +36,10 @@
 #include <lauxlib.h>
 #include <lualib.h>
 #include <sio.h>
+#include "box/lua/trigger.h"
 
 #include "box/box.h"
+#include "box/error.h"
 #include "box/session.h"
 #include "box/user.h"
 #include "box/schema.h"
@@ -274,31 +276,10 @@ lbox_session_peer(struct lua_State *L)
 	return 1;
 }
 
-/**
- * run on_connect|on_disconnect trigger
- */
-static int
-lbox_push_on_connect_event(struct lua_State *L, void *event)
-{
-	(void) L;
-	(void) event;
-	return 0;
-}
-
-static int
-lbox_push_on_auth_event(struct lua_State *L, void *event)
-{
-	struct on_auth_trigger_ctx *ctx = (struct on_auth_trigger_ctx *)event;
-	lua_pushlstring(L, ctx->user_name, ctx->user_name_len);
-	lua_pushboolean(L, ctx->is_authenticated);
-	return 2;
-}
-
 static int
 lbox_session_on_connect(struct lua_State *L)
 {
-	return lbox_trigger_reset(L, 2, &session_on_connect,
-				  lbox_push_on_connect_event, NULL);
+	return luaT_event_reset_trigger(L, 1, session_on_connect_event);
 }
 
 static int
@@ -313,8 +294,7 @@ lbox_session_run_on_connect(struct lua_State *L)
 static int
 lbox_session_on_disconnect(struct lua_State *L)
 {
-	return lbox_trigger_reset(L, 2, &session_on_disconnect,
-				  lbox_push_on_connect_event, NULL);
+	return luaT_event_reset_trigger(L, 1, session_on_disconnect_event);
 }
 
 static int
@@ -329,8 +309,7 @@ lbox_session_run_on_disconnect(struct lua_State *L)
 static int
 lbox_session_on_auth(struct lua_State *L)
 {
-	return lbox_trigger_reset(L, 2, &session_on_auth,
-				  lbox_push_on_auth_event, NULL);
+	return luaT_event_reset_trigger(L, 1, session_on_auth_event);
 }
 
 static int
@@ -351,16 +330,6 @@ lbox_session_run_on_auth(struct lua_State *L)
 	return 0;
 }
 
-static int
-lbox_push_on_access_denied_event(struct lua_State *L, void *event)
-{
-	struct on_access_denied_ctx *ctx = (struct on_access_denied_ctx *) event;
-	lua_pushstring(L, ctx->access_type);
-	lua_pushstring(L, ctx->object_type);
-	lua_pushstring(L, ctx->object_name);
-	return 3;
-}
-
 /**
  * Push a message using a protocol, depending on a session type.
  * @param L Lua state. First argument on the stack is data to
@@ -391,8 +360,7 @@ lbox_session_push(struct lua_State *L)
 static int
 lbox_session_on_access_denied(struct lua_State *L)
 {
-	return lbox_trigger_reset(L, 2, &on_access_denied,
-				  lbox_push_on_access_denied_event, NULL);
+	return luaT_event_reset_trigger(L, 1, on_access_denied_event);
 }
 
 static int
diff --git a/src/box/lua/trigger.c b/src/box/lua/trigger.c
index 7a21c3b46e..d62758ba75 100644
--- a/src/box/lua/trigger.c
+++ b/src/box/lua/trigger.c
@@ -9,6 +9,7 @@
 #include <diag.h>
 #include <fiber.h>
 #include "lua/utils.h"
+#include "tt_static.h"
 
 /**
  * Sets a trigger with passed name to the passed event.
@@ -253,3 +254,149 @@ box_lua_trigger_init(struct lua_State *L)
 	luaL_register_type(L, event_trigger_iterator_typename,
 			   trigger_iterator_methods);
 }
+
+/** Old API compatibility. */
+
+/**
+ * Checks positional arguments for luaT_event_reset_trigger.
+ * Throws an error if the format is not suitable.
+ */
+static void
+luaT_event_reset_trigger_check_positional_input(struct lua_State *L, int bottom)
+{
+	/* Push optional arguments. */
+	lua_settop(L, bottom + 2);
+
+	/*
+	 * (nil, callable) is OK, deletes the trigger
+	 * (callable, nil), is OK, adds the trigger
+	 * (callable, callable) is OK, replaces the trigger
+	 * no arguments is OK, lists all trigger
+	 * anything else is error.
+	 */
+	bool ok = true;
+	/* Name must be a string if it is passed. */
+	ok = ok && (lua_isnil(L, bottom + 2) || luaL_isnull(L, bottom + 2) ||
+		    lua_type(L, bottom + 2) == LUA_TSTRING);
+	ok = ok && (lua_isnil(L, bottom + 1) || luaL_isnull(L, bottom + 1) ||
+		    luaL_iscallable(L, bottom + 1));
+	ok = ok && (lua_isnil(L, bottom) || luaL_isnull(L, bottom) ||
+		    luaL_iscallable(L, bottom));
+	if (!ok)
+		luaL_error(L, "trigger reset: incorrect arguments");
+}
+
+/**
+ * Sets or deletes trigger by name depending on passed arguments. Value at
+ * name_idx must be a string, value at func_idx must be a callable object,
+ * nil or box.NULL. Otherwise, an error will be thrown.
+ */
+static int
+luaT_event_reset_trigger_by_name(struct lua_State *L, struct event *event,
+				 int name_idx, int func_idx)
+{
+	if (lua_type(L, name_idx) != LUA_TSTRING)
+		luaL_error(L, "name must be a string");
+	const char *trigger_name = lua_tostring(L, name_idx);
+	if (luaL_iscallable(L, func_idx)) {
+		struct func_adapter *func =
+			func_adapter_lua_create(L, func_idx);
+		event_reset_trigger(event, trigger_name, func);
+		lua_pushvalue(L, func_idx);
+		return 1;
+	} else if (lua_isnil(L, func_idx) || luaL_isnull(L, func_idx)) {
+		event_reset_trigger(event, trigger_name, NULL);
+		return 0;
+	}
+	return luaL_error(L, "func must be a callable object or nil");
+}
+
+int
+luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event)
+{
+	assert(L != NULL);
+	assert(bottom >= 1);
+	assert(event != NULL);
+	/* Use key-value API if the first argument is a non-callable table. */
+	if (lua_gettop(L) == bottom && lua_istable(L, -1) &&
+	    !luaL_iscallable(L, -1)) {
+		lua_getfield(L, bottom, "name");
+		lua_getfield(L, bottom, "func");
+		return luaT_event_reset_trigger_by_name(L, event, -2, -1);
+	}
+	/* Old way with name support. */
+	luaT_event_reset_trigger_check_positional_input(L, bottom);
+	const int top = bottom + 2;
+	if (!lua_isnil(L, top) && !luaL_isnull(L, top))
+		return luaT_event_reset_trigger_by_name(L, event, top, bottom);
+	/*
+	 * Name is not passed - old API support.
+	 * 1. If triggers are not passed, return table of triggers.
+	 * 2. If new_trigger is passed and old_trigger is not - set
+	 * new_trigger using its address as name.
+	 * 3. If old_trigger is passed and new_trigger is not - delete
+	 * trigger by address of old_trigger as a name.
+	 * 4. If both triggers are provided - replace old trigger with
+	 * new one if they have the same address, delete old trigger and
+	 * insert new one at the beginning of the trigger list otherwise.
+	 */
+	if (!luaL_iscallable(L, bottom) && !luaL_iscallable(L, bottom + 1)) {
+		lua_createtable(L, 0, 0);
+		const char *name = NULL;
+		struct func_adapter *trigger = NULL;
+		struct event_trigger_iterator it;
+		event_trigger_iterator_create(&it, event);
+		int idx = 0;
+		while (event_trigger_iterator_next(&it, &trigger, &name)) {
+			idx++;
+			func_adapter_lua_get_func(trigger, L);
+			lua_rawseti(L, -2, idx);
+		}
+		event_trigger_iterator_destroy(&it);
+		return 1;
+	}
+	int ret_count = 0;
+	const void *old_handler = NULL;
+	const void *new_handler = NULL;
+	const char *old_name = NULL;
+	const char *new_name = NULL;
+	struct func_adapter *new_trg = NULL;
+	if (luaL_iscallable(L, bottom + 1)) {
+		old_handler = lua_topointer(L, bottom + 1);
+		old_name = tt_sprintf("%p", old_handler);
+		if (event_find_trigger(event, old_name) == NULL)
+			luaL_error(L, "trigger reset: Trigger is not found");
+	}
+	if (luaL_iscallable(L, bottom)) {
+		new_handler = lua_topointer(L, bottom);
+		new_name = tt_sprintf("%p", new_handler);
+		new_trg = func_adapter_lua_create(L, bottom);
+		lua_pushvalue(L, bottom);
+		ret_count = 1;
+	}
+	if (new_handler != NULL && old_handler != NULL) {
+		if (old_handler == new_handler) {
+			event_reset_trigger(event, new_name, new_trg);
+		} else {
+			/*
+			 * Need to reference the event because it can be
+			 * deleted after deleting all its triggers.
+			 */
+			event_ref(event);
+			event_reset_trigger(event, old_name, NULL);
+			/*
+			 * Delete a trigger with new name to surely place the
+			 * new trigger at the beginning of the trigger list.
+			 */
+			event_reset_trigger(event, new_name, NULL);
+			event_reset_trigger(event, new_name, new_trg);
+			event_unref(event);
+		}
+	} else if (old_handler != NULL) {
+		event_reset_trigger(event, old_name, NULL);
+	} else {
+		assert(new_handler != NULL);
+		event_reset_trigger(event, new_name, new_trg);
+	}
+	return ret_count;
+}
diff --git a/src/box/lua/trigger.h b/src/box/lua/trigger.h
index 6b1f1b443a..e7eaf5bc41 100644
--- a/src/box/lua/trigger.h
+++ b/src/box/lua/trigger.h
@@ -9,12 +9,55 @@
 extern "C" {
 #endif /* defined(__cplusplus) */
 
+struct event;
+
 /**
  * Initializes module trigger.
  */
 void
 box_lua_trigger_init(struct lua_State *L);
 
+/**
+ * Creates a Lua trigger, replaces an existing one, or deletes a trigger.
+ *
+ * The function accepts a Lua stack. Values starting from index bottom are
+ * considered as the function arguments.
+ *
+ * The function supports two API versions.
+ *
+ * The first version - key-value arguments. The function is called with one Lua
+ * argument which is not callable table. In this case, the table must contain
+ * key "name" with value of type string - the name of a trigger. The second
+ * key, "func", is optional. If it is not present, a trigger with passed name
+ * is deleted, or no-op, if there is no such trigger. If key "func" is present,
+ * it must contain a callable object as a value - it will be used as a handler
+ * for a new trigger. The new trigger will be appended to the beginning of the
+ * trigger list or replace an existing one with the same name. The function
+ * returns new trigger (or nothing, if it was deleted).
+ *
+ * The second version - positional arguments. The function is called with up to
+ * three Lua arguments. The first one is a new trigger handler - it must be a
+ * callable object or nil. The second one is an old trigger handler - it must
+ * be a callable object or nil as well. The third argument is a trigger name of
+ * type string (it can be nil too).
+ * If the name is passed, the logic is equivalent to key-value API -
+ * the third argument is a trigger name, the first one is a trigger handler (or
+ * nil if the function is called to delete a trigger by name), the second
+ * argument is ignored, but the type check is still performed. If the name is
+ * not passed, the function mimics the behavior of function lbox_trigger_reset:
+ * 1. If triggers (first and second arguments) are not passed, returns table of
+ * triggers.
+ * 2. If new trigger is passed and old one is not - sets new trigger using
+ * its address as name. The new trigger is returned.
+ * 3. If old trigger is passed and new trigger is not - deletes a trigger by
+ * address of an old trigger as a name. Returns nothing.
+ * 4. If both triggers are provided - replace old trigger with new one if they
+ * have the same address, delete old trigger and insert new one at the beginning
+ * of the trigger list otherwise. The new trigger is returned.
+ */
+int
+luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/box/schema.h b/src/box/schema.h
index 65f82cdaa0..a9930a56a1 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -146,28 +146,11 @@ sequence_cache_delete(uint32_t id);
  */
 extern struct rlist on_alter_sequence;
 
-/**
- * Triggers fired after access denied error is created.
- */
-extern struct rlist on_access_denied;
-
 /**
  * Triggers fired after committing a change in _func space.
  */
 extern struct rlist on_alter_func;
 
-/**
- * Context passed to on_access_denied trigger.
- */
-struct on_access_denied_ctx {
-	/** Type of declined access */
-	const char *access_type;
-	/** Type of object the required access was denied to */
-	const char *object_type;
-	/** Name of object the required access was denied to */
-	const char *object_name;
-};
-
 /** Global grants to classes of objects. */
 struct entity_access {
        struct access space[BOX_USER_MAX];
diff --git a/src/box/session.c b/src/box/session.c
index fb1be86805..5d3ac8f3a7 100644
--- a/src/box/session.c
+++ b/src/box/session.c
@@ -30,6 +30,7 @@
  */
 #include "session.h"
 #include "fiber.h"
+#include "func_adapter.h"
 #include "fiber_cond.h"
 #include "sio.h"
 #include "memory.h"
@@ -43,6 +44,8 @@
 #include "on_shutdown.h"
 #include "sql.h"
 #include "tweaks.h"
+#include "event.h"
+#include "schema.h"
 
 const char *session_type_strs[] = {
 	"background",
@@ -77,6 +80,9 @@ static struct fiber_cond shutdown_list_empty_cond;
 RLIST_HEAD(session_on_connect);
 RLIST_HEAD(session_on_disconnect);
 RLIST_HEAD(session_on_auth);
+struct event *session_on_connect_event;
+struct event *session_on_disconnect_event;
+struct event *session_on_auth_event;
 
 static inline uint64_t
 sid_max(void)
@@ -321,8 +327,13 @@ session_remove_stmt_id(struct session *session, uint32_t stmt_id)
  */
 struct credentials admin_credentials;
 
+/**
+ * Runs on_connect or on_disconnect triggers. Argument triggers is for internal
+ * triggers and argument event is for user-defined ones.
+ */
 static int
-session_run_triggers(struct session *session, struct rlist *triggers)
+session_run_triggers(struct session *session, struct rlist *triggers,
+		     struct event *event)
 {
 	struct fiber *fiber = fiber();
 	assert(session == current_session());
@@ -331,7 +342,21 @@ session_run_triggers(struct session *session, struct rlist *triggers)
 	fiber_set_user(fiber, &admin_credentials);
 
 	int rc = trigger_run(triggers, NULL);
-
+	if (rc != 0)
+		goto out;
+
+	const char *name = NULL;
+	struct func_adapter *trigger = NULL;
+	struct func_adapter_ctx ctx;
+	struct event_trigger_iterator it;
+	event_trigger_iterator_create(&it, event);
+	while (rc == 0 && event_trigger_iterator_next(&it, &trigger, &name)) {
+		func_adapter_begin(trigger, &ctx);
+		rc = func_adapter_call(trigger, &ctx);
+		func_adapter_end(trigger, &ctx);
+	}
+	event_trigger_iterator_destroy(&it);
+out:
 	/* Restore original credentials */
 	fiber_set_user(fiber, &session->credentials);
 
@@ -341,20 +366,40 @@ session_run_triggers(struct session *session, struct rlist *triggers)
 void
 session_run_on_disconnect_triggers(struct session *session)
 {
-	if (session_run_triggers(session, &session_on_disconnect) != 0)
+	if (session_run_triggers(session, &session_on_disconnect,
+				 session_on_disconnect_event) != 0)
 		diag_log();
 }
 
 int
 session_run_on_connect_triggers(struct session *session)
 {
-	return session_run_triggers(session, &session_on_connect);
+	return session_run_triggers(session, &session_on_connect,
+				    session_on_connect_event);
 }
 
 int
 session_run_on_auth_triggers(const struct on_auth_trigger_ctx *result)
 {
-	return trigger_run(&session_on_auth, (void *)result);
+	if (trigger_run(&session_on_auth, (void *)result) != 0)
+		return -1;
+
+	const char *name = NULL;
+	struct func_adapter *trigger = NULL;
+	struct func_adapter_ctx ctx;
+	struct event_trigger_iterator it;
+	int rc = 0;
+	event_trigger_iterator_create(&it, session_on_auth_event);
+	while (rc == 0 && event_trigger_iterator_next(&it, &trigger, &name)) {
+		func_adapter_begin(trigger, &ctx);
+		func_adapter_push_str(trigger, &ctx, result->user_name,
+				      result->user_name_len);
+		func_adapter_push_bool(trigger, &ctx, result->is_authenticated);
+		rc = func_adapter_call(trigger, &ctx);
+		func_adapter_end(trigger, &ctx);
+	}
+	event_trigger_iterator_destroy(&it);
+	return rc;
 }
 
 void
@@ -421,6 +466,16 @@ session_init(void)
 {
 	for (int type = 0; type < session_type_MAX; type++)
 		session_vtab_registry[type] = generic_session_vtab;
+	session_on_connect_event = event_get("box.session.on_connect", true);
+	event_ref(session_on_connect_event);
+	session_on_disconnect_event =
+		event_get("box.session.on_disconnect", true);
+	event_ref(session_on_disconnect_event);
+	session_on_auth_event = event_get("box.session.on_auth", true);
+	event_ref(session_on_auth_event);
+	on_access_denied_event =
+		event_get("box.session.on_access_denied", true);
+	event_ref(on_access_denied_event);
 	session_registry = mh_i64ptr_new();
 	mempool_create(&session_pool, &cord()->slabc, sizeof(struct session));
 	credentials_create(&admin_credentials, admin_user);
@@ -433,6 +488,14 @@ session_init(void)
 void
 session_free(void)
 {
+	event_unref(session_on_connect_event);
+	session_on_connect_event = NULL;
+	event_unref(session_on_disconnect_event);
+	session_on_disconnect_event = NULL;
+	event_unref(session_on_auth_event);
+	session_on_auth_event = NULL;
+	event_unref(on_access_denied_event);
+	on_access_denied_event = NULL;
 	if (session_registry)
 		mh_i64ptr_delete(session_registry);
 	credentials_destroy(&admin_credentials);
diff --git a/src/box/session.h b/src/box/session.h
index a0575b850a..2fb7832e7f 100644
--- a/src/box/session.h
+++ b/src/box/session.h
@@ -188,8 +188,11 @@ session_find(uint64_t sid);
 
 /** Global on-connect triggers. */
 extern struct rlist session_on_connect;
+extern struct event *session_on_connect_event;
 
+/** Global on-auth triggers. */
 extern struct rlist session_on_auth;
+extern struct event *session_on_auth_event;
 
 /**
  * Get the current session from @a fiber
@@ -292,6 +295,7 @@ effective_user(void)
 
 /** Global on-disconnect triggers. */
 extern struct rlist session_on_disconnect;
+extern struct event *session_on_disconnect_event;
 
 void
 session_storage_cleanup(int sid);
diff --git a/test/box-luatest/triggers_old_api_test.lua b/test/box-luatest/triggers_old_api_test.lua
new file mode 100644
index 0000000000..ed276f9869
--- /dev/null
+++ b/test/box-luatest/triggers_old_api_test.lua
@@ -0,0 +1,209 @@
+local server = require('luatest.server')
+local t = require('luatest')
+
+local g = t.group()
+
+g.before_all(function()
+    g.server = server:new({alias = 'master'})
+    g.server:start()
+    g.server:exec(function()
+        -- Every element of this table is an array of 2 elements. The first
+        -- one is a function that sets triggers, and the second one is the name
+        -- of the associated event in the trigger registry. The second element
+        -- is optional.
+        rawset(_G, 'old_api_triggers', {
+            {box.session.on_connect, 'box.session.on_connect'},
+            {box.session.on_disconnect, 'box.session.on_disconnect'},
+            {box.session.on_auth, 'box.session.on_auth'},
+        })
+
+        rawset(_G, 'ffi_monotonic_id', 0)
+
+        -- Helper that generates callable table, userdata and cdata
+        local function generate_callable_objects(handler)
+            local ffi = require('ffi')
+            local a = {}
+            local mt = {__call = handler}
+            setmetatable(a, mt)
+
+            local b = newproxy(true)
+            mt = getmetatable(b)
+            mt.__call = handler
+
+            local ffi_id = rawget(_G, 'ffi_monotonic_id')
+            rawset(_G, 'ffi_monotonic_id', ffi_id + 1)
+            local struct_name = string.format('foo%d', ffi_id)
+            ffi.cdef(string.format('struct %s { int x; }', struct_name))
+            mt = {__call = handler}
+            ffi.metatype(string.format('struct %s', struct_name), mt)
+            local c = ffi.new(string.format('struct %s', struct_name), {x = 42})
+            return a, b, c
+        end
+        rawset(_G, 'generate_callable_objects', generate_callable_objects)
+    end)
+end)
+
+g.after_all(function()
+    g.server:stop()
+end)
+
+g.test_old_api = function()
+    g.server:exec(function()
+        local old_api_triggers = rawget(_G, 'old_api_triggers')
+        local generate_callable_objects =
+            rawget(_G, 'generate_callable_objects')
+        local function check_trigger(old_api_trigger)
+            local function h1() end
+            local h2, h3, h4 = generate_callable_objects(h1)
+
+            t.assert_equals(old_api_trigger(), {})
+
+            -- Invalid arguments
+            t.assert_error_msg_content_equals(
+                'trigger reset: incorrect arguments', old_api_trigger, nil,
+                nil, {1, 2})
+            t.assert_error_msg_content_equals(
+                'trigger reset: incorrect arguments', old_api_trigger, nil,
+                nil, h1)
+            t.assert_error_msg_content_equals(
+                'trigger reset: incorrect arguments', old_api_trigger, 'abc')
+            t.assert_error_msg_content_equals(
+                'trigger reset: incorrect arguments', old_api_trigger, nil,
+                'abc')
+            t.assert_error_msg_content_equals(
+                'trigger reset: incorrect arguments', old_api_trigger, 'abc',
+                'abc')
+            t.assert_error_msg_content_equals(
+                'trigger reset: incorrect arguments', old_api_trigger, 'abc',
+                nil, 'name')
+            t.assert_error_msg_content_equals(
+                'trigger reset: Trigger is not found', old_api_trigger, nil, h1)
+
+            -- Set and delete
+            t.assert_equals(old_api_trigger(h1), h1)
+            t.assert_equals(old_api_trigger(h2), h2)
+            t.assert_equals(old_api_trigger(h3), h3)
+            t.assert_equals(old_api_trigger(h4), h4)
+            t.assert_equals(old_api_trigger(), {h4, h3, h2, h1})
+            t.assert_equals(old_api_trigger(nil, h4), nil)
+            t.assert_equals(old_api_trigger(), {h3, h2, h1})
+            t.assert_equals(old_api_trigger(h2), h2)
+            t.assert_equals(old_api_trigger(), {h3, h2, h1})
+            t.assert_equals(old_api_trigger(h3, box.NULL, box.NULL), h3)
+            t.assert_equals(old_api_trigger(), {h3, h2, h1})
+
+            -- Replace
+            -- Expected behavior: when both triggers has the same name, the
+            -- order is preserved. Otherwise, old trigger is deleted and the new
+            -- one is inserted at the beginning of the list. Also, when the
+            -- names are different, trigger with the new name should be deleted
+            -- to prevent name duplication.
+            t.assert_equals(old_api_trigger(h4, h2), h4)
+            t.assert_equals(old_api_trigger(), {h4, h3, h1})
+            t.assert_equals(old_api_trigger(h1, h3), h1)
+            t.assert_equals(old_api_trigger(), {h1, h4})
+            t.assert_equals(old_api_trigger(h3), h3)
+            t.assert_equals(old_api_trigger(), {h3, h1, h4})
+            t.assert_equals(old_api_trigger(h1, h1), h1)
+            t.assert_equals(old_api_trigger(), {h3, h1, h4})
+            t.assert_equals(old_api_trigger(nil, h3), nil)
+            t.assert_equals(old_api_trigger(), {h1, h4})
+            t.assert_equals(old_api_trigger(box.NULL, h4), nil)
+            t.assert_equals(old_api_trigger(), {h1})
+            t.assert_equals(old_api_trigger(h2, h1), h2)
+            t.assert_equals(old_api_trigger(), {h2})
+            t.assert_equals(old_api_trigger(nil, h2, box.NULL), nil)
+            t.assert_equals(old_api_trigger(), {})
+
+            -- Name argument
+            t.assert_equals(old_api_trigger(h1, nil, 't1'), h1)
+            t.assert_equals(old_api_trigger(), {h1})
+            -- Check if the second argument will be ignored
+            t.assert_equals(old_api_trigger(h2, h1, 't2'), h2)
+            t.assert_equals(old_api_trigger(), {h2, h1})
+            t.assert_equals(old_api_trigger(h3, nil, 't1'), h3)
+            t.assert_equals(old_api_trigger(), {h2, h3})
+            t.assert_equals(old_api_trigger(nil, h1, 't1'), nil)
+            t.assert_equals(old_api_trigger(), {h2})
+            t.assert_equals(old_api_trigger(nil, nil, 't2'), nil)
+            t.assert_equals(old_api_trigger(), {})
+        end
+        for _, trg_descr in pairs(old_api_triggers) do
+            local trg = trg_descr[1]
+            check_trigger(trg)
+        end
+    end)
+end
+
+g.test_key_value_args = function()
+    g.server:exec(function()
+        local old_api_triggers = rawget(_G, 'old_api_triggers')
+        local generate_callable_objects =
+            rawget(_G, 'generate_callable_objects')
+        local function check_trigger(old_api_trigger)
+            local function h1() end
+            local h2, h3, h4 = generate_callable_objects(h1)
+
+            t.assert_equals(old_api_trigger(), {})
+
+            -- Invalid arguments
+            t.assert_error_msg_content_equals(
+                "trigger reset: incorrect arguments", old_api_trigger,
+                {func = h1, name = 'trg'}, h1)
+            t.assert_error_msg_content_equals(
+                "trigger reset: incorrect arguments", old_api_trigger,
+                {func = h1, name = 'trg'}, 'abc')
+            t.assert_error_msg_content_equals(
+                "func must be a callable object or nil", old_api_trigger,
+                {func = 'str', name = 'trg'})
+            t.assert_error_msg_content_equals(
+                "name must be a string", old_api_trigger, {func = h1})
+            t.assert_error_msg_content_equals(
+                "name must be a string", old_api_trigger,
+                {func = h1, name = box.NULL})
+
+            -- Set and delete
+            t.assert_equals(old_api_trigger{func = h1, name = 'h1'}, h1)
+            t.assert_equals(old_api_trigger{func = h2, name = 'h2'}, h2)
+            t.assert_equals(old_api_trigger{func = h3, name = 'h3'}, h3)
+            t.assert_equals(old_api_trigger{func = h4, name = 'h4'}, h4)
+            t.assert_equals(old_api_trigger(), {h4, h3, h2, h1})
+            t.assert_equals(old_api_trigger{name = 'h4'}, nil)
+            t.assert_equals(old_api_trigger(), {h3, h2, h1})
+            t.assert_equals(old_api_trigger{name = 'h2', func = box.NULL}, nil)
+            t.assert_equals(old_api_trigger(), {h3, h1})
+            t.assert_equals(old_api_trigger{name = 'h3', func = box.NULL}, nil)
+            t.assert_equals(old_api_trigger(), {h1})
+            t.assert_equals(old_api_trigger{name = 'h1'}, nil)
+            t.assert_equals(old_api_trigger(), {})
+        end
+        for _, trg_descr in pairs(old_api_triggers) do
+            local trg = trg_descr[1]
+            check_trigger(trg)
+        end
+    end)
+end
+
+g.test_associated_event = function()
+    g.server:exec(function()
+        local trigger = require('trigger')
+        local old_api_triggers = rawget(_G, 'old_api_triggers')
+        local function check_trigger(old_api_trigger, event_name)
+            local trg = {"I am the trigger of event " .. event_name}
+            local mt = {__call = function() end}
+            setmetatable(trg, mt)
+            old_api_trigger(trg)
+            local event_triggers = trigger.info(event_name)[event_name]
+            t.assert_equals(#event_triggers, 1)
+            t.assert_equals(event_triggers[1][2], trg)
+            old_api_trigger(nil, trg)
+        end
+        for _, trg_descr in pairs(old_api_triggers) do
+            local trg = trg_descr[1]
+            local event_name = trg_descr[2]
+            if event_name ~= nil then
+                check_trigger(trg, event_name)
+            end
+        end
+    end)
+end
diff --git a/test/unit/luaT_tuple_new.c b/test/unit/luaT_tuple_new.c
index 42643464c5..4e8d2a5569 100644
--- a/test/unit/luaT_tuple_new.c
+++ b/test/unit/luaT_tuple_new.c
@@ -4,6 +4,7 @@
 #include <lualib.h>           /* luaL_openlibs() */
 #include "memory.h"           /* memory_init() */
 #include "fiber.h"            /* fiber_init() */
+#include "event.h"            /* event_init() */
 #include "small/ibuf.h"       /* struct ibuf */
 #include "box/box.h"          /* box_init() */
 #include "box/tuple.h"        /* box_tuple_format_default() */
@@ -187,6 +188,7 @@ main()
 
 	struct lua_State *L = luaT_newteststate();
 
+	event_init();
 	box_init();
 	tarantool_lua_error_init(L);
 	luaopen_msgpack(L);
diff --git a/test/unit/tuple_format.c b/test/unit/tuple_format.c
index b3d2cd04b6..98f9899fd5 100644
--- a/test/unit/tuple_format.c
+++ b/test/unit/tuple_format.c
@@ -7,6 +7,7 @@
 
 #include "coll/coll.h"
 
+#include "core/event.h"
 #include "core/fiber.h"
 #include "core/memory.h"
 
@@ -230,12 +231,14 @@ main(void)
 	fiber_init(fiber_c_invoke);
 	coll_init();
 	tuple_init(test_field_name_hash);
+	event_init();
 	box_init();
 	sql_init();
 
 	int rc = test_tuple_format();
 
 	box_free();
+	event_free();
 	tuple_free();
 	coll_free();
 	fiber_free();
-- 
GitLab