From e6c77fc45f1b1ee3095fc809adbc0493517053c2 Mon Sep 17 00:00:00 2001
From: Dmitry Ivanov <ivadmi5@gmail.com>
Date: Wed, 5 Jul 2023 16:30:20 +0300
Subject: [PATCH] feat: Add `auth_type` to box.schema.user.create()

Now it's possible to specify the desired authentication method during
user creation via `auth_type`, e.g.

```lua
box.schema.user.create('mickey', { auth_type = 'chap-sha1',
                                   password = 'foobar' })
```

Furthermore, authentication methods may now specify that they don't
require password to create stored authentication info. This is used
in LDAP authentication (`auth_type = 'ldap'`):

```lua
box.schema.user.create('mickey', { auth_type = 'ldap' })
```

NO_DOC=picodata internal patch
NO_CHANGELOG=picodata internal patch
NO_TEST=picodata internal patch
---
 src/box/auth_ldap.c      |  2 +-
 src/box/authentication.h |  6 ++++++
 src/box/lua/misc.cc      | 23 +++++++++++++++++++++++
 src/box/lua/schema.lua   | 28 +++++++++++++++++++++-------
 4 files changed, 51 insertions(+), 8 deletions(-)

diff --git a/src/box/auth_ldap.c b/src/box/auth_ldap.c
index 40bedced6b..1eae02e3e1 100644
--- a/src/box/auth_ldap.c
+++ b/src/box/auth_ldap.c
@@ -294,7 +294,7 @@ auth_ldap_new(void)
 {
 	struct auth_method *method = xmalloc(sizeof(*method));
 	method->name = AUTH_LDAP_NAME;
-	method->flags = 0;
+	method->flags = AUTH_METHOD_PASSWORDLESS_DATA_PREPARE;
 	method->auth_method_delete = auth_ldap_delete;
 	method->auth_data_prepare = auth_ldap_data_prepare;
 	method->auth_request_prepare = auth_ldap_request_prepare;
diff --git a/src/box/authentication.h b/src/box/authentication.h
index 35fd4cfe21..a843a1b8cf 100644
--- a/src/box/authentication.h
+++ b/src/box/authentication.h
@@ -53,6 +53,12 @@ enum auth_method_flag {
 	 * communication channel is encrypted (e.g. with SSL/TLS).
 	 */
 	AUTH_METHOD_REQUIRES_ENCRYPTION = 1 << 0,
+	/**
+	 * Set if the authentication method does not need
+	 * password in auth_method::auth_data_prepare.
+	 * This is opt-in because most methods require password.
+	 */
+	AUTH_METHOD_PASSWORDLESS_DATA_PREPARE = 1 << 1,
 };
 
 /**
diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc
index 1d0c517bb6..f0676873af 100644
--- a/src/box/lua/misc.cc
+++ b/src/box/lua/misc.cc
@@ -236,6 +236,28 @@ lbox_generate_space_id(lua_State *L)
 
 /** {{{ Helper that generates user auth data. **/
 
+/**
+ * Takes authentication method name (e.g. 'chap-sha1').
+ * Returns true if password is needed to produce auth data, false otherwise.
+ * Raises Lua error if the specified authentication method doesn't exist.
+ */
+static int
+lbox_prepare_auth_needs_password(lua_State *L)
+{
+	size_t method_name_len;
+	const char *method_name = luaL_checklstring(L, 1, &method_name_len);
+	const struct auth_method *method = auth_method_by_name(method_name,
+							       method_name_len);
+	if (method == NULL) {
+		diag_set(ClientError, ER_UNKNOWN_AUTH_METHOD,
+			 tt_cstr(method_name, method_name_len));
+		return luaT_error(L);
+	}
+	int pwdless = method->flags & AUTH_METHOD_PASSWORDLESS_DATA_PREPARE;
+	lua_pushboolean(L, !pwdless);
+	return 1;
+}
+
 /**
  * Takes authentication method name (e.g. 'chap-sha1'), password and user name.
  * Returns authentication data that can be stored in the _user space.
@@ -507,6 +529,7 @@ void
 box_lua_misc_init(struct lua_State *L)
 {
 	static const struct luaL_Reg boxlib_internal[] = {
+		{"prepare_auth_needs_password", lbox_prepare_auth_needs_password},
 		{"prepare_auth", lbox_prepare_auth},
 		{"select", lbox_select},
 		{"new_tuple_format", lbox_tuple_format_new},
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index ea072a4562..12a439ad3c 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -3205,10 +3205,14 @@ box.schema.user.password = function(password, name)
     return internal.prepare_auth(box.cfg.auth_type, password, name)
 end
 
-local function prepare_auth_list(password, name)
+local function prepare_auth_list_needs_password(auth_type)
+    return internal.prepare_auth_needs_password(auth_type)
+end
+
+local function prepare_auth_list(auth_type, password, name)
     return {
-        [box.cfg.auth_type] =
-            internal.prepare_auth(box.cfg.auth_type, password, name)
+        [auth_type] =
+            internal.prepare_auth(auth_type, password, name)
     }
 end
 
@@ -3228,9 +3232,11 @@ end
 
 local function chpasswd(uid, new_password, name)
     local _user = box.space[box.schema.USER_ID]
+    local auth_type = box.cfg.auth_type
+    local auth_list = prepare_auth_list(auth_type, new_password, name)
     local auth_history = prepare_auth_history(uid)
     check_password(new_password, auth_history)
-    _user:update({uid}, {{'=', 5, prepare_auth_list(new_password, name)},
+    _user:update({uid}, {{'=', 5, auth_list},
                          {'=', 6, auth_history},
                          {'=', 7, math.floor(fiber.time())}})
 end
@@ -3257,17 +3263,25 @@ end
 box.schema.user.create = function(name, opts)
     local uid = user_or_role_resolve(name)
     opts = opts or {}
-    check_param_table(opts, { password = 'string', if_not_exists = 'boolean' })
+    check_param_table(opts, {auth_type = 'string',
+                             password = 'string',
+                             if_not_exists = 'boolean'})
     if uid then
         if not opts.if_not_exists then
             box.error(box.error.USER_EXISTS, name)
         end
         return
     end
+    local auth_type = opts.auth_type or box.cfg.auth_type
     local auth_list
-    if opts.password then
+    if not prepare_auth_list_needs_password(auth_type) then
+        if opts.password then
+            log.warn(auth_type .. " doesn't need password")
+        end
+        auth_list = prepare_auth_list(auth_type, '', name)
+    elseif opts.password then
         check_password(opts.password)
-        auth_list = prepare_auth_list(opts.password, name)
+        auth_list = prepare_auth_list(auth_type, opts.password, name)
     else
         auth_list = setmap({})
     end
-- 
GitLab