diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf index a5113ba28ee7fcbe61a3e6b75f2fccf29332a3ec..dc35cccaf2a5667686d2b582becfdbb430ddab8d 100644 --- a/doc/sql/query.ebnf +++ b/doc/sql/query.ebnf @@ -3,10 +3,11 @@ EXPLAIN ::= 'EXPLAIN' ( DML | DQL ) DQL ::= (SELECT | SELECT UNION ALL SELECT | SELECT (EXCEPT 'DISTINCT'? ) SELECT ) Options? DML ::= (DELETE | INSERT | UPDATE) Options? DDL ::= CreateTable | DropTable -ACL ::= DropRole | DropUser | CreateRole | CreateUser +ACL ::= DropRole | DropUser | CreateRole | CreateUser | AlterUser CreateRole ::= 'create role' role ('option' '(' ('timeout' '=' DOUBLE)')')? CreateTable ::= 'create table' table '(' Column (',' Column)* ',' PrimaryKey ')' ('using' ('memtx' | 'vinyl'))? Distribution ('option' '(' ('timeout' '=' DOUBLE)')')? -CreateUser ::= 'create user' user 'with'? 'password' "'" password "'" ('using' ('chap-sha1' | 'ldap' | 'md5'))? ('option' '(' ('timeout' '=' DOUBLE)')')? +CreateUser ::= 'create user' user 'with'? 'password' "'" password "'" ('using' ('chap-sha1' | 'ldap' | 'md5'))? ('option' '(' ('timeout' '=' DOUBLE)')')? +AlterUser ::= 'alter user' user 'with'? 'password' "'" password "'" ('using' ('chap-sha1' | 'ldap' | 'md5'))? ('option' '(' ('timeout' '=' DOUBLE)')')? Column ::= name ('Bool' | 'Decimal' | 'Double' | 'Int' | 'Number' | 'Scalar' | 'String' | 'Text' | 'Unsigned' | 'Varchar') (('not'?) 'null')? PrimaryKey ::= 'primary key' '(' name (',' name)* ')' Distribution ::= 'global' | ('distributed by' '(' name (',' name)* ')') diff --git a/sbroad-cartridge/test_app/test/integration/acl_test.lua b/sbroad-cartridge/test_app/test/integration/acl_test.lua index 55341f31cdbd83d84a6ad68a02797a45763c537d..99f987eb23c53bdb82cea543b9c8a093f95c3cbc 100644 --- a/sbroad-cartridge/test_app/test/integration/acl_test.lua +++ b/sbroad-cartridge/test_app/test/integration/acl_test.lua @@ -68,3 +68,16 @@ g.test_create_user = function() [[Sbroad Error: ACL queries are not supported]] ) end + +g.test_alter_user = function() + local api = cluster:server("api-1").net_box + + local _, err = api:call( + "sbroad.execute", + { [[ ALTER USER "user" WITH PASSWORD '123' USING MD5 ]], {} } + ) + t.assert_equals( + string.format("%s", err), + [[Sbroad Error: ACL queries are not supported]] + ) +end diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index a52babb146a3a8c1fcc4722033ee8ba0a4a4013d..26a0d6d291860cb2cced0484416e728ee97b40a6 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -427,6 +427,76 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, Ok(create_sharded_table) } +/// Code block common for CREATE/ALTER USER moved to separate function. +fn parse_create_alter_user( + ast: &AbstractSyntaxTree, + node: &ParseNode, + default_timeout: Decimal, +) -> Result<(String, String, String, Decimal), SbroadError> { + let mut iter = node.children.iter(); + // User name + let user_name_id = iter.next().ok_or_else(|| { + SbroadError::Invalid( + Entity::ParseNode, + Some(String::from("UserName expected as a first child")), + ) + })?; + let user_name_node = ast.nodes.get_node(*user_name_id)?; + let user_name = + normalize_name_for_space_api(user_name_node.value.as_ref().ok_or_else(|| { + SbroadError::NotFound(Entity::Node, "user name in UserName node".into()) + })?); + // Password + let pwd_id = iter.next().ok_or_else(|| { + SbroadError::Invalid( + Entity::ParseNode, + Some(String::from("Password expected as a second child")), + ) + })?; + let pwd_node = ast.nodes.get_node(*pwd_id)?; + let password: String = pwd_node + .value + .as_ref() + .ok_or_else(|| SbroadError::NotFound(Entity::Node, "password in Password node".into()))? + .to_string(); + // Optional parameters: auth method and timeout. + let mut timeout = default_timeout; + let mut auth_method = String::from("chap-sha1"); + for child_id in iter { + let child_node = ast.nodes.get_node(*child_id)?; + match child_node.rule { + Type::Timeout => { + timeout = get_timeout(ast, *child_id)?; + } + Type::AuthMethod => { + let method_id = child_node.children.first().ok_or_else(|| { + SbroadError::Invalid( + Entity::ParseNode, + Some(String::from("Method expected under AuthMethod node")), + ) + })?; + let method_node = ast.nodes.get_node(*method_id)?; + auth_method = method_node + .value + .as_ref() + .ok_or_else(|| { + SbroadError::NotFound(Entity::Node, "auth method in AuthMethod node".into()) + })? + .to_string(); + } + _ => { + return Err(SbroadError::Invalid( + Entity::Node, + Some(format!( + "ACL node contains unexpected child: {child_node:?}", + )), + )); + } + } + } + Ok((user_name, password, auth_method, timeout)) +} + impl Ast for AbstractSyntaxTree { /// Build an empty AST. fn empty() -> Self { @@ -2009,81 +2079,21 @@ impl Ast for AbstractSyntaxTree { let plan_id = plan.nodes.push(Node::Ddl(drop_table)); map.add(id, plan_id); } + Type::AlterUser => { + let (user_name, password, auth_method, timeout) = + parse_create_alter_user(self, node, default_timeout)?; + let alter_user = Acl::AlterUser { + name: user_name, + password, + auth_method, + timeout, + }; + let plan_id = plan.nodes.push(Node::Acl(alter_user)); + map.add(id, plan_id); + } Type::CreateUser => { - let mut iter = node.children.iter(); - // User name - let user_name_id = iter.next().ok_or_else(|| { - SbroadError::Invalid( - Entity::ParseNode, - Some(String::from("UserName expected under CreateUser node")), - ) - })?; - let user_name_node = self.nodes.get_node(*user_name_id)?; - let user_name = normalize_name_for_space_api( - user_name_node.value.as_ref().ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - "user name in the create user AST".into(), - ) - })?, - ); - // Password - let pwd_id = iter.next().ok_or_else(|| { - SbroadError::Invalid( - Entity::ParseNode, - Some(String::from("Password expected under CreateUser node")), - ) - })?; - let pwd_node = self.nodes.get_node(*pwd_id)?; - let password: String = pwd_node - .value - .as_ref() - .ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - "password in the create user AST".into(), - ) - })? - .to_string(); - // Optional parameters: auth method and timeout. - let mut timeout = default_timeout; - let mut auth_method = String::from("chap-sha1"); - for child_id in iter { - let child_node = self.nodes.get_node(*child_id)?; - match child_node.rule { - Type::Timeout => { - timeout = get_timeout(self, *child_id)?; - } - Type::AuthMethod => { - let method_id = child_node.children.first().ok_or_else(|| { - SbroadError::Invalid( - Entity::ParseNode, - Some(String::from("Method expected under AuthMethod node")), - ) - })?; - let method_node = self.nodes.get_node(*method_id)?; - auth_method = method_node - .value - .as_ref() - .ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - "auth method in the create user AST".into(), - ) - })? - .to_string(); - } - _ => { - return Err(SbroadError::Invalid( - Entity::Node, - Some(format!( - "AST create user node {:?} contains unexpected children", - child_node, - )), - )); - } - } - } + let (user_name, password, auth_method, timeout) = + parse_create_alter_user(self, node, default_timeout)?; let create_user = Acl::CreateUser { name: user_name, password, diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs index e5393d3e9ab752d95a965a11f7128c19a3bdeeee..40f58b1774d92fede1a941105671533cb69907cc 100644 --- a/sbroad-core/src/frontend/sql/ast.rs +++ b/sbroad-core/src/frontend/sql/ast.rs @@ -29,6 +29,7 @@ pub enum Type { Addition, Alias, AliasName, + AlterUser, And, ArithmeticExpr, ArithParentheses, @@ -161,6 +162,7 @@ impl Type { Rule::Addition => Ok(Type::Addition), Rule::Alias => Ok(Type::Alias), Rule::AliasName => Ok(Type::AliasName), + Rule::AlterUser => Ok(Type::AlterUser), Rule::And => Ok(Type::And), Rule::ArithmeticExpr => Ok(Type::ArithmeticExpr), Rule::ArithParentheses => Ok(Type::ArithParentheses), @@ -298,6 +300,7 @@ impl fmt::Display for Type { Type::Addition => "Addition".to_string(), Type::Alias => "Alias".to_string(), Type::AliasName => "AliasName".to_string(), + Type::AlterUser => "AlterUser".to_string(), Type::And => "And".to_string(), Type::ArithmeticExpr => "ArithmeticExpr".to_string(), Type::ArithParentheses => "ArithParentheses".to_string(), diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 0eb0022ef6031bd0e0c22bf5389d6322ce42b839..1cf5118dde02335030324d09923e8c3a89e0ada5 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -1,12 +1,16 @@ Command = _{ SOI ~ (ExplainQuery | Query | DDL | ACL) ~ EOF } -ACL = _{ DropRole | DropUser | CreateRole | CreateUser } +ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser } CreateUser = { ^"create" ~ ^"user" ~ UserName ~ (^"with")? ~ ^"password" ~ PasswordString ~ AuthMethod? ~ Option? } PasswordString = _{ "'" ~ Password ~ "'" } Password = @{ String } + AlterUser = { + ^"alter" ~ ^"user" ~ UserName ~ (^"with")? ~ ^"password" ~ PasswordString ~ + AuthMethod? ~ Option? + } AuthMethod = { ^"using" ~ (ChapSha1 | Md5 | Ldap) } ChapSha1 = { ^"chap-sha1" } Md5 = { ^"md5" } diff --git a/sbroad-core/src/ir/acl.rs b/sbroad-core/src/ir/acl.rs index 0d8d3a2a03e659c00ea29e64cb12da8a8572a084..029d512862dc202aba3ee4a62e37f5a80499d322 100644 --- a/sbroad-core/src/ir/acl.rs +++ b/sbroad-core/src/ir/acl.rs @@ -22,6 +22,12 @@ pub enum Acl { auth_method: String, timeout: Decimal, }, + AlterUser { + name: String, + password: String, + auth_method: String, + timeout: Decimal, + }, } impl Acl { @@ -34,6 +40,7 @@ impl Acl { Acl::DropRole { ref timeout, .. } | Acl::DropUser { ref timeout, .. } | Acl::CreateRole { ref timeout, .. } + | Acl::AlterUser { ref timeout, .. } | Acl::CreateUser { ref timeout, .. } => timeout, } .to_string()