From 076a842011e09c84c25fb5e68f1b23c9917a3750 Mon Sep 17 00:00:00 2001
From: Konstantin Nazarov <mail@racktear.com>
Date: Fri, 1 Jul 2016 15:55:32 +0300
Subject: [PATCH] Permit empty passwords in net.box

This is required to do the following:

tarantoolctl user@host
tarantoolctl guest@host
tarantoolctl guest:@host

Currently all of those will lead to error from net.box. Either because
password is not specified, or because guest user can't login with empty
password.

This change effectively sets guest password to an empty string, to make
it less of a special case to authentication.

And since now guest can be authenticated with an empty password, net.box
will replace nil passwords with empty strings, when user name is
provided.

We still support the case when username=nil and password=nil. In this
case net.box doesn't send authentication packet at all. Guest login is
just assumed.

The bootstrap snapshot will now have guest user with an empty password,
instead of no password.

Fixes #1545
---
 src/box/alter.cc             |  14 +++-
 src/box/bootstrap.snap       | Bin 4925 -> 4965 bytes
 src/box/lua/net_box.lua      |   3 +-
 src/box/lua/upgrade.lua      |  16 +++++
 test/box-py/bootstrap.result |   4 +-
 test/box/access_misc.result  |   2 +-
 test/box/auth.result         | 136 +++++++++++++++++++++++++++++++++--
 test/box/auth.test.lua       |  49 ++++++++++++-
 test/box/net.box.result      |  31 ++++++--
 test/box/net.box.test.lua    |  14 ++--
 test/xlog/upgrade.result     |   4 +-
 11 files changed, 249 insertions(+), 24 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index cd20343e0c..e209f162fe 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -79,6 +79,12 @@
 /** _func columns */
 #define FUNC_LANGUAGE    4
 
+/**
+ * chap-sha1 of empty string, i.e.
+ * base64_encode(sha1(sha1(""))
+ */
+#define CHAP_SHA1_EMPTY_PASSWORD "vhvewKp0tNyweZQ+cFKAlsyphfg="
+
 /* {{{ Auxiliary functions and methods. */
 
 void
@@ -1509,6 +1515,12 @@ user_def_fill_auth_data(struct user_def *user, const char *auth_data)
 			tnt_raise(ClientError, ER_CREATE_USER,
 				  user->name, "invalid user password");
 		}
+                if (user->uid == GUEST) {
+                    /** Guest user is permitted to have empty password */
+                    if (strncmp(hash2_base64, CHAP_SHA1_EMPTY_PASSWORD, len))
+                        tnt_raise(ClientError, ER_GUEST_USER_PASSWORD);
+                }
+
 		base64_decode(hash2_base64, len, user->hash2,
 			      sizeof(user->hash2));
 		break;
@@ -1549,8 +1561,6 @@ user_def_create_from_tuple(struct user_def *user, struct tuple *tuple)
 				tnt_raise(ClientError, ER_CREATE_ROLE,
 					  user->name, "authentication "
 					  "data can not be set for a role");
-			if (user->uid == GUEST)
-				tnt_raise(ClientError, ER_GUEST_USER_PASSWORD);
 		}
 		user_def_fill_auth_data(user, auth_data);
 	}
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 34378bae7f3b1dda8c715d8cef73eab048982bb7..4bdaf0187f31436f4fa3a4c47171de679ba0d63a 100644
GIT binary patch
delta 312
zcmdn1_EfDt*v~P5%RtZ2h$}d?s4TU}O2IJI!X(i&DOESk+#*TWB+W8SH_^z<RM*5T
z(Kyv8&D6}?z>q7<IVV3k+e)F@&`QCemW%7^F7DMf4Cf|2YMi+|00NpAn3x%x1kN!q
zFbXJ6TArL!T3nJ^bW<fYG0`l=$TCGY+04j99B8aTlCEW%xuvd|nW<??im7?Bsgc3N
zX+|u{=>l;R&&V^eGi-d@&L8B=aIVte?33lo5XKuoj5ko6%D}iZy)?DBWJzgpYEk3L
z<c!1u-QtWy!#!mgWvS)f1qLO4mF1~Xf!fJ#-i|rNl?54T>9(8a2}CimJm{LuIr)!}
Nqy{k_zbdfyEdcj&WeNZQ

delta 292
zcmaE=wpXn_*v~P5%RtZ2h$}d?s4TU}N+HR>JjL81F-_MrG1)}d#30RB*U~gONjEVy
zIWg7LJk7u)#f&S=IVV3k+e)F@&`QCemW%7^F7DMf4CjJ>zM8l^00NpAn3x%x1kN!q
zFbXJ6TArL!T3nJ^bW_DV%`(Nr%)mm|$kaSlT-U@jDOK0PA~8kR#3;?g%*@E##Lzr(
z;xr?cT{0J*O*|vd#Ky7lZ9Bh~G{d>8AKVWuUxqN=0Ajp>;uHqPrRk-q#U)Ehi&Kj>
g9}|dTa&@1QvJY9Y9YnE%;v~kKD#Up5s=(T}03qaFH~;_u

diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 48721f9f08..e0c1c1f961 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -335,8 +335,7 @@ local remote_methods = {
         end
 
         if self.opts.user ~= nil and self.opts.password == nil then
-            box.error(box.error.PROC_LUA,
-                "net.box: password is not defined")
+            self.opts.password = ""
         end
         if self.opts.user == nil and self.opts.password ~= nil then
             box.error(box.error.PROC_LUA,
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index bc7cefa2c0..128d9ed424 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -469,6 +469,21 @@ local function upgrade_to_1_6_8()
     box.space._schema:replace({'version', 1, 6, 8})
 end
 
+local function upgrade_users_to_1_7_0()
+    box.schema.user.passwd('guest', '')
+end
+
+local function upgrade_to_1_7_0()
+    if VERSION_ID >= version_id(1, 7, 0) then
+        return
+    end
+
+    upgrade_users_to_1_7_0()
+
+    log.info("set schema version to 1.7.0")
+    box.space._schema:replace({'version', 1, 7, 0})
+end
+
 --------------------------------------------------------------------------------
 
 local function upgrade()
@@ -483,6 +498,7 @@ local function upgrade()
     VERSION_ID = version_id(major, minor, patch)
 
     upgrade_to_1_6_8()
+    upgrade_to_1_7_0()
 end
 
 local function bootstrap()
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index a3cf08af5c..9e51825866 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -5,7 +5,7 @@ box.space._schema:select{}
 ---
 - - ['cluster', '<cluster uuid>']
   - ['max_id', 511]
-  - ['version', 1, 6, 8]
+  - ['version', 1, 7, 0]
 ...
 box.space._cluster:select{}
 ---
@@ -83,7 +83,7 @@ box.space._index:select{}
 ...
 box.space._user:select{}
 ---
-- - [0, 1, 'guest', 'user']
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
   - [1, 1, 'admin', 'user']
   - [2, 1, 'public', 'role']
   - [3, 1, 'replication', 'role']
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 9120b9ed34..9ed1388576 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -573,7 +573,7 @@ s:drop()
 ...
 box.space._user:select()
 ---
-- - [0, 1, 'guest', 'user']
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
   - [1, 1, 'admin', 'user']
   - [2, 1, 'public', 'role']
   - [3, 1, 'replication', 'role']
diff --git a/test/box/auth.result b/test/box/auth.result
index 478ef191fd..e0e7cf1bda 100644
--- a/test/box/auth.result
+++ b/test/box/auth.result
@@ -16,6 +16,12 @@ box.schema.user.create('test', {password='pass'})
 box.schema.user.grant('test', 'read,write,execute', 'universe')
 ---
 ...
+box.schema.user.create('test2', {password=''})
+---
+...
+box.schema.user.grant('test2', 'read,write,execute', 'universe')
+---
+...
 box.schema.user.grant('guest', 'read,write,execute', 'universe')
 ---
 ...
@@ -48,9 +54,6 @@ type(handle)
 handle2 = session.on_auth(auth_trigger2)
 ---
 ...
-counter = 0
----
-...
 msg = ''
 ---
 ...
@@ -58,6 +61,9 @@ LISTEN = require('uri').parse(box.cfg.listen)
 ---
 ...
 -- check connection with authentication(counter must be incremented)
+counter = 0
+---
+...
 c = net.box:new('test:pass@' .. LISTEN.host .. ':' .. LISTEN.service)
 ---
 ...
@@ -72,7 +78,129 @@ msg
 ---
 - user test is there
 ...
+counter = 0
+---
+...
+c = net.box:new('test2:@' .. LISTEN.host .. ':' .. LISTEN.service)
+---
+...
+while counter < 1 do fiber.sleep(0.001) end
+---
+...
+counter
+---
+- 1
+...
+msg
+---
+- user test2 is there
+...
+counter = 0
+---
+...
+c = net.box:new('test2@' .. LISTEN.host .. ':' .. LISTEN.service)
+---
+...
+while counter < 1 do fiber.sleep(0.001) end
+---
+...
+counter
+---
+- 1
+...
+msg
+---
+- user test2 is there
+...
+counter = 0
+---
+...
+c1 = net.box:new(LISTEN.host, LISTEN.service, {user='test2'})
+---
+...
+while counter < 1 do fiber.sleep(0.001) end
+---
+...
+counter
+---
+- 1
+...
+msg
+---
+- user test2 is there
+...
+counter = 0
+---
+...
+c1 = net.box:new('guest@' .. LISTEN.host .. ':' .. LISTEN.service)
+---
+...
+while counter < 1 do fiber.sleep(0.001) end
+---
+...
+counter
+---
+- 1
+...
+msg
+---
+- user guest is there
+...
+counter = 0
+---
+...
+c1 = net.box:new('guest:@' .. LISTEN.host .. ':' .. LISTEN.service)
+---
+...
+while counter < 1 do fiber.sleep(0.001) end
+---
+...
+counter
+---
+- 1
+...
+msg
+---
+- user guest is there
+...
+counter = 0
+---
+...
+c1 = net.box:new(LISTEN.host, LISTEN.service, {user='guest', password=''})
+---
+...
+while counter < 1 do fiber.sleep(0.001) end
+---
+...
+counter
+---
+- 1
+...
+msg
+---
+- user guest is there
+...
+counter = 0
+---
+...
+c1 = net.box:new(LISTEN.host, LISTEN.service, {user='guest'})
+---
+...
+while counter < 1 do fiber.sleep(0.001) end
+---
+...
+counter
+---
+- 1
+...
+msg
+---
+- user guest is there
+...
 -- check guest connection without authentication(no increment)
+counter = 0
+---
+...
 c1 = net.box:new(LISTEN.host, LISTEN.service)
 ---
 ...
@@ -82,7 +210,7 @@ c1:ping()
 ...
 counter
 ---
-- 1
+- 0
 ...
 -- cleanup
 c:close()
diff --git a/test/box/auth.test.lua b/test/box/auth.test.lua
index a8d65465df..bf5ca1f10e 100644
--- a/test/box/auth.test.lua
+++ b/test/box/auth.test.lua
@@ -5,6 +5,8 @@ space = box.schema.space.create('tweedledum')
 index = space:create_index('primary', { type = 'hash' })
 box.schema.user.create('test', {password='pass'})
 box.schema.user.grant('test', 'read,write,execute', 'universe')
+box.schema.user.create('test2', {password=''})
+box.schema.user.grant('test2', 'read,write,execute', 'universe')
 box.schema.user.grant('guest', 'read,write,execute', 'universe')
 
 -- check how authentication trigger work
@@ -21,18 +23,63 @@ type(handle)
 -- check triggers list
 #session.on_auth()
 handle2 = session.on_auth(auth_trigger2)
-counter = 0
+
 msg = ''
 
 LISTEN = require('uri').parse(box.cfg.listen)
 
 -- check connection with authentication(counter must be incremented)
+counter = 0
 c = net.box:new('test:pass@' .. LISTEN.host .. ':' .. LISTEN.service)
 while counter < 1 do fiber.sleep(0.001) end
 counter
 msg
 
+counter = 0
+c = net.box:new('test2:@' .. LISTEN.host .. ':' .. LISTEN.service)
+while counter < 1 do fiber.sleep(0.001) end
+counter
+msg
+
+counter = 0
+c = net.box:new('test2@' .. LISTEN.host .. ':' .. LISTEN.service)
+while counter < 1 do fiber.sleep(0.001) end
+counter
+msg
+
+counter = 0
+c1 = net.box:new(LISTEN.host, LISTEN.service, {user='test2'})
+while counter < 1 do fiber.sleep(0.001) end
+counter
+msg
+
+counter = 0
+c1 = net.box:new('guest@' .. LISTEN.host .. ':' .. LISTEN.service)
+while counter < 1 do fiber.sleep(0.001) end
+counter
+msg
+
+counter = 0
+c1 = net.box:new('guest:@' .. LISTEN.host .. ':' .. LISTEN.service)
+while counter < 1 do fiber.sleep(0.001) end
+counter
+msg
+
+counter = 0
+c1 = net.box:new(LISTEN.host, LISTEN.service, {user='guest', password=''})
+while counter < 1 do fiber.sleep(0.001) end
+counter
+msg
+
+counter = 0
+c1 = net.box:new(LISTEN.host, LISTEN.service, {user='guest'})
+while counter < 1 do fiber.sleep(0.001) end
+counter
+msg
+
+
 -- check guest connection without authentication(no increment)
+counter = 0
 c1 = net.box:new(LISTEN.host, LISTEN.service)
 c1:ping()
 counter
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 6f0532658d..0404326f1f 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -767,15 +767,28 @@ cnc:console('a = {1, 2, 3, 4}; return a[3]')
 
 '
 ...
--- #545 user or password is not defined
-remote:new(LISTEN.host, LISTEN.service, { user = 'test' })
+-- #1545 empty password
+cn = remote:new(LISTEN.host, LISTEN.service, { user = 'test' })
 ---
-- error: 'net.box: password is not defined'
 ...
-remote:new(LISTEN.host, LISTEN.service, { password = 'test' })
+cn ~= nil
+---
+- true
+...
+cn:close()
+---
+...
+cn = remote:new(LISTEN.host, LISTEN.service, { password = 'test' })
 ---
 - error: 'net.box: user is not defined'
 ...
+cn ~= nil
+---
+- true
+...
+cn:close()
+---
+...
 -- #544 usage for remote[point]method
 cn = remote:new(LISTEN.host, LISTEN.service)
 ---
@@ -820,9 +833,15 @@ cn:close()
 uri = string.format('%s@%s:%s', 'netbox', LISTEN.host, LISTEN.service)
 ---
 ...
-remote.new(uri)
+cn = remote.new(uri)
+---
+...
+cn ~= nil
+---
+- true
+...
+cn:close()
 ---
-- error: 'net.box: password is not defined'
 ...
 cn = remote.new(uri, { password = 'test' })
 ---
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 02471ad574..2a42136b3e 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -287,9 +287,13 @@ cnc:console('return 1, 2, 3, "string", nil')
 cnc:console('error("test")')
 cnc:console('a = {1, 2, 3, 4}; return a[3]')
 
--- #545 user or password is not defined
-remote:new(LISTEN.host, LISTEN.service, { user = 'test' })
-remote:new(LISTEN.host, LISTEN.service, { password = 'test' })
+-- #1545 empty password
+cn = remote:new(LISTEN.host, LISTEN.service, { user = 'test' })
+cn ~= nil
+cn:close()
+cn = remote:new(LISTEN.host, LISTEN.service, { password = 'test' })
+cn ~= nil
+cn:close()
 
 -- #544 usage for remote[point]method
 cn = remote:new(LISTEN.host, LISTEN.service)
@@ -313,7 +317,9 @@ cn:ping()
 cn:close()
 
 uri = string.format('%s@%s:%s', 'netbox', LISTEN.host, LISTEN.service)
-remote.new(uri)
+cn = remote.new(uri)
+cn ~= nil
+cn:close()
 cn = remote.new(uri, { password = 'test' })
 cn:ping()
 cn:close()
diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
index 3cc6a1d8fb..33a1bc0dc3 100644
--- a/test/xlog/upgrade.result
+++ b/test/xlog/upgrade.result
@@ -36,7 +36,7 @@ box.space._schema:select()
 ---
 - - ['cluster', '<server_uuid>']
   - ['max_id', 513]
-  - ['version', 1, 6, 8]
+  - ['version', 1, 7, 0]
 ...
 box.space._space:select()
 ---
@@ -116,7 +116,7 @@ box.space._index:select()
 ...
 box.space._user:select()
 ---
-- - [0, 1, 'guest', 'user']
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
   - [1, 1, 'admin', 'user']
   - [2, 1, 'public', 'role']
   - [3, 1, 'replication', 'role']
-- 
GitLab