diff --git a/src/args.rs b/src/args.rs index a4db68e74752d76f1897ac3f362308e49b1dc391..20b8b0d68c47593dd50567672052218014e756ca 100644 --- a/src/args.rs +++ b/src/args.rs @@ -11,6 +11,7 @@ use thiserror::Error; use crate::failure_domain::FailureDomain; use crate::instance::InstanceId; use crate::replicaset::ReplicasetId; +use crate::schema::AuthMethod; use crate::util::Uppercase; #[derive(Debug, Parser)] @@ -316,13 +317,23 @@ pub struct Connect { #[clap( short = 'u', long = "user", - value_name = "user", + value_name = "USER", default_value = "guest", env = "PICODATA_USER" )] /// The username to connect with. Ignored if provided in <ADDRESS>. pub user: String, + #[clap( + short = 'a', + long = "auth-type", + value_name = "METHOD", + default_value = AuthMethod::ChapSha1.as_str(), + arg_enum, + )] + /// The preferred authentication method. + pub auth_method: AuthMethod, + #[clap(value_name = "ADDRESS")] /// Picodata instance address. Format: `[user@][host][:port]` pub address: Address, diff --git a/src/luamod.lua b/src/luamod.lua index 3c2f6eb94032929f94ad573ae681ec4a310460f1..f7237aedb3daf0dac9d3b1c76bfefc7f5c0f5fdb 100644 --- a/src/luamod.lua +++ b/src/luamod.lua @@ -191,6 +191,7 @@ Params: 2. password (string) 3. opts (table) - timeout (number), seconds + - auth_type (string) Returns: @@ -203,7 +204,10 @@ function pico.create_user(user, password, opts) box.internal.check_param(user, 'user', 'string') box.internal.check_param(password, 'password', 'string') -- TODO: check password requirements. - box.internal.check_param_table(opts, { timeout = 'number' }) + box.internal.check_param_table(opts, { + timeout = 'number', + auth_type = 'string', + }) opts = opts or {} if not opts.timeout then box.error(box.error.ILLEGAL_PARAMS, 'opts.timeout is mandatory') @@ -217,7 +221,7 @@ function pico.create_user(user, password, opts) -- XXX: we construct this closure every time the function is called, -- which is bad for performance/jit. Refactor if problems are discovered. - local auth_type = box.cfg.auth_type + local auth_type = opts.auth_type or box.cfg.auth_type local auth_data = box.internal.prepare_auth(auth_type, password, user) local function make_op_if_needed() local grantee_def = box.space._user.index.name:get(user) @@ -270,6 +274,7 @@ Params: 2. password (string) 3. opts (table) - timeout (number), seconds + - auth_type (string) Returns: @@ -281,7 +286,10 @@ function pico.change_password(user, password, opts) local ok, err = pcall(function() box.internal.check_param(user, 'user', 'string') box.internal.check_param(password, 'password', 'string') - box.internal.check_param_table(opts, { timeout = 'number' }) + box.internal.check_param_table(opts, { + timeout = 'number', + auth_type = 'string', + }) opts = opts or {} if not opts.timeout then box.error(box.error.ILLEGAL_PARAMS, 'opts.timeout is mandatory') @@ -297,7 +305,7 @@ function pico.change_password(user, password, opts) -- XXX: we construct this closure every time the function is called, -- which is bad for performance/jit. Refactor if problems are discovered. - local auth_type = box.cfg.auth_type + local auth_type = opts.auth_type or box.cfg.auth_type local auth_data = box.internal.prepare_auth(auth_type, password, user) local function make_op_if_needed() -- TODO: allow `user` to be a user id instead of name diff --git a/src/main.rs b/src/main.rs index adcfc12c1cda24a3a026d181100e3fdb3240fdf2..191e8baf575b6ea8b2c36b7fe93ca815744827d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -254,8 +254,8 @@ fn main_connect(args: args::Connect) -> ! { }; let address = format!( - "{user}:{password}@{}:{}", - args.address.host, args.address.port + "{user}:{password}@{}:{}?auth_type={}", + args.address.host, args.address.port, args.auth_method ); let rc = tarantool_main!( diff --git a/src/schema.rs b/src/schema.rs index f63b8c10c61f9afa5c7a0798660fb45da702dd11..3c9e3e9df836cd7050874d57a1e4a39947e81286 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -196,6 +196,7 @@ pub struct AuthDef { } ::tarantool::define_str_enum! { + #[derive(clap::ArgEnum)] pub enum AuthMethod { ChapSha1 = "chap-sha1", MD5 = "md5", diff --git a/tarantool-sys b/tarantool-sys index 4056a2006d93e2cd8a96a52b689deacab640d48e..ea487f51c1b7ad2e6d6731e64958b37041d82395 160000 --- a/tarantool-sys +++ b/tarantool-sys @@ -1 +1 @@ -Subproject commit 4056a2006d93e2cd8a96a52b689deacab640d48e +Subproject commit ea487f51c1b7ad2e6d6731e64958b37041d82395 diff --git a/test/int/test_cli_connect.py b/test/int/test_cli_connect.py index 411c822c67193c40cee8c56b9b8e20c75833bcea..68527813529b4a24acbd75b41074dbc7072f9b03 100644 --- a/test/int/test_cli_connect.py +++ b/test/int/test_cli_connect.py @@ -162,3 +162,61 @@ def test_connection_refused(binary_path: str): cli.expect_exact("Connection is not established") cli.expect_exact(pexpect.EOF) + + +def test_connect_auth_type_ok(i1: Instance): + cli = pexpect.spawn( + command=i1.binary_path, + args=["connect", f"{i1.host}:{i1.port}", "-u", "testuser", "-a", "chap-sha1"], + encoding="utf-8", + timeout=1, + ) + cli.logfile = sys.stdout + + cli.expect_exact("Enter password for testuser: ") + cli.sendline("testpass") + + cli.expect_exact(f"connected to {i1.host}:{i1.port}") + cli.expect_exact(f"{i1.host}:{i1.port}>") + + cli.sendline("box.session.user()") + cli.expect_exact("---\r\n") + cli.expect_exact("- testuser\r\n") + cli.expect_exact("...\r\n") + cli.expect_exact("\r\n") + + eprint("^D") + cli.sendcontrol("d") + cli.expect_exact(pexpect.EOF) + + +def test_connect_auth_type_different(i1: Instance): + cli = pexpect.spawn( + command=i1.binary_path, + args=["connect", f"{i1.host}:{i1.port}", "-u", "testuser", "-a", "chap-sha1"], + encoding="utf-8", + timeout=1, + ) + cli.logfile = sys.stdout + + cli.expect_exact("Enter password for testuser: ") + cli.sendline("") + + cli.expect_exact("Connection is not established") + cli.expect_exact(pexpect.EOF) + + +def test_connect_auth_type_unknown(binary_path: str): + cli = pexpect.spawn( + command=binary_path, + args=["connect", ":0", "-u", "testuser", "-a", "deadbeef"], + env={"NO_COLOR": "1"}, + encoding="utf-8", + timeout=1, + ) + cli.logfile = sys.stdout + + cli.expect_exact( + "error: \"deadbeef\" isn't a valid value for '--auth-type <METHOD>" + ) + cli.expect_exact(pexpect.EOF)