From 0718595808be5535b0fbcd4eec476252b1d8e339 Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Mon, 18 Sep 2023 17:51:01 +0300
Subject: [PATCH] feat: implement CREATE USER

---
 doc/sql/query.ebnf                            |  3 +-
 .../test_app/test/integration/acl_test.lua    | 15 +++-
 sbroad-core/src/frontend/sql.rs               | 89 +++++++++++++++++++
 sbroad-core/src/frontend/sql/ast.rs           | 28 ++++--
 sbroad-core/src/frontend/sql/query.pest       | 12 ++-
 sbroad-core/src/ir/acl.rs                     | 13 ++-
 6 files changed, 150 insertions(+), 10 deletions(-)

diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index a859f97e30..d6723d5b89 100644
--- a/doc/sql/query.ebnf
+++ b/doc/sql/query.ebnf
@@ -3,8 +3,9 @@ EXPLAIN     ::= 'EXPLAIN' ( DML | DQL )
 DQL         ::= (SELECT | SELECT UNION ALL SELECT | SELECT (EXCEPT 'DISTINCT'? ) SELECT ) Options?
 DML         ::= (DELETE | INSERT) Options?
 DDL         ::= CreateTable | DropTable
-ACL         ::= DropUser
+ACL         ::= CreateUser | DropUser
 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)')')?
 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 d9256d69f8..d77db6fc99 100644
--- a/sbroad-cartridge/test_app/test/integration/acl_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/acl_test.lua
@@ -28,4 +28,17 @@ g.test_drop_user = function()
             string.format("%s", err),
             [[Sbroad Error: ACL queries are not supported]]
     )
-end
\ No newline at end of file
+end
+
+g.test_create_user = function()
+    local api = cluster:server("api-1").net_box
+
+    local _, err = api:call(
+            "sbroad.execute",
+            { [[ CREATE 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 f8157bb50a..af3d400ddf 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -1915,6 +1915,90 @@ impl Ast for AbstractSyntaxTree {
                     let plan_id = plan.nodes.push(Node::Ddl(drop_table));
                     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 create_user = Acl::CreateUser {
+                        name: user_name,
+                        password,
+                        auth_method,
+                        timeout,
+                    };
+                    let plan_id = plan.nodes.push(Node::Acl(create_user));
+                    map.add(id, plan_id);
+                }
                 Type::DropUser => {
                     let user_name_id = node.children.first().ok_or_else(|| {
                         SbroadError::Invalid(
@@ -1945,6 +2029,8 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Add
                 | Type::AliasName
+                | Type::AuthMethod
+                | Type::ChapSha1
                 | Type::Columns
                 | Type::ColumnDef
                 | Type::ColumnDefName
@@ -1968,15 +2054,18 @@ impl Ast for AbstractSyntaxTree {
                 | Type::GtEq
                 | Type::In
                 | Type::InnerJoinKind
+                | Type::Ldap
                 | Type::LeftJoinKind
                 | Type::Length
                 | Type::Lt
                 | Type::LtEq
+                | Type::Md5
                 | Type::Memtx
                 | Type::Multiply
                 | Type::NewTable
                 | Type::NotEq
                 | Type::NotFlag
+                | Type::Password
                 | Type::PrimaryKey
                 | Type::PrimaryKeyColumn
                 | Type::ScanName
diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs
index 9d87f2110a..f6cefa94fc 100644
--- a/sbroad-core/src/frontend/sql/ast.rs
+++ b/sbroad-core/src/frontend/sql/ast.rs
@@ -33,10 +33,13 @@ pub enum Type {
     ArithmeticExpr,
     ArithParentheses,
     Asterisk,
+    AuthMethod,
     Between,
     Cast,
+    ChapSha1,
     Cmp,
     CreateTable,
+    CreateUser,
     Column,
     Columns,
     ColumnDef,
@@ -82,9 +85,11 @@ pub enum Type {
     DoReplace,
     DoNothing,
     DoFail,
+    Ldap,
+    LeftJoinKind,
     Lt,
     LtEq,
-    LeftJoinKind,
+    Md5,
     Memtx,
     Multiplication,
     Multiply,
@@ -95,6 +100,7 @@ pub enum Type {
     NotFlag,
     Null,
     Or,
+    Password,
     Query,
     SqlVdbeMaxSteps,
     VTableMaxRows,
@@ -156,10 +162,13 @@ impl Type {
             Rule::ArithmeticExpr => Ok(Type::ArithmeticExpr),
             Rule::ArithParentheses => Ok(Type::ArithParentheses),
             Rule::Asterisk => Ok(Type::Asterisk),
+            Rule::AuthMethod => Ok(Type::AuthMethod),
             Rule::Between => Ok(Type::Between),
             Rule::Cast => Ok(Type::Cast),
+            Rule::ChapSha1 => Ok(Type::ChapSha1),
             Rule::Cmp => Ok(Type::Cmp),
             Rule::CreateTable => Ok(Type::CreateTable),
+            Rule::CreateUser => Ok(Type::CreateUser),
             Rule::Column => Ok(Type::Column),
             Rule::ColumnDef => Ok(Type::ColumnDef),
             Rule::ColumnDefName => Ok(Type::ColumnDefName),
@@ -204,10 +213,12 @@ impl Type {
             Rule::Insert => Ok(Type::Insert),
             Rule::Integer => Ok(Type::Integer),
             Rule::IsNull => Ok(Type::IsNull),
+            Rule::Ldap => Ok(Type::Ldap),
             Rule::Length => Ok(Type::Length),
             Rule::LeftJoinKind => Ok(Type::LeftJoinKind),
             Rule::Lt => Ok(Type::Lt),
             Rule::LtEq => Ok(Type::LtEq),
+            Rule::Md5 => Ok(Type::Md5),
             Rule::Memtx => Ok(Type::Memtx),
             Rule::Multiplication => Ok(Type::Multiplication),
             Rule::Multiply => Ok(Type::Multiply),
@@ -218,6 +229,7 @@ impl Type {
             Rule::NotFlag => Ok(Type::NotFlag),
             Rule::Null => Ok(Type::Null),
             Rule::Or => Ok(Type::Or),
+            Rule::Password => Ok(Type::Password),
             Rule::Query => Ok(Type::Query),
             Rule::SqlVdbeMaxSteps => Ok(Type::SqlVdbeMaxSteps),
             Rule::VTableMaxRows => Ok(Type::VTableMaxRows),
@@ -284,10 +296,13 @@ impl fmt::Display for Type {
             Type::ArithmeticExpr => "ArithmeticExpr".to_string(),
             Type::ArithParentheses => "ArithParentheses".to_string(),
             Type::Asterisk => "Asterisk".to_string(),
+            Type::AuthMethod => "AuthMethod".to_string(),
             Type::Between => "Between".to_string(),
             Type::Cast => "Cast".to_string(),
+            Type::ChapSha1 => "ChapSha1".to_string(),
             Type::Cmp => "Cmp".to_string(),
             Type::CreateTable => "CreateTable".to_string(),
+            Type::CreateUser => "CreateUser".to_string(),
             Type::Column => "Column".to_string(),
             Type::ColumnDef => "ColumnDef".to_string(),
             Type::ColumnDefIsNull => "ColumnDefIsNull".to_string(),
@@ -330,10 +345,12 @@ impl fmt::Display for Type {
             Type::Insert => "Insert".to_string(),
             Type::Integer => "Integer".to_string(),
             Type::IsNull => "IsNull".to_string(),
+            Type::Ldap => "Ldap".to_string(),
             Type::Length => "Length".to_string(),
             Type::LeftJoinKind => "left".to_string(),
             Type::Lt => "Lt".to_string(),
             Type::LtEq => "LtEq".to_string(),
+            Type::Md5 => "Md5".to_string(),
             Type::Memtx => "Memtx".to_string(),
             Type::Multiplication => "Multiplication".to_string(),
             Type::Multiply => "Multiply".to_string(),
@@ -343,16 +360,15 @@ impl fmt::Display for Type {
             Type::NotEq => "NotEq".to_string(),
             Type::NotFlag => "NotFlag".to_string(),
             Type::Null => "Null".to_string(),
-            Type::Query => "Query".to_string(),
-            Type::VTableMaxRows => "vtable_max_rows".to_string(),
-            Type::SqlVdbeMaxSteps => "sql_vdbe_max_steps".to_string(),
             Type::Or => "Or".to_string(),
             Type::Parameter => "Parameter".to_string(),
             Type::Parentheses => "Parentheses".to_string(),
+            Type::Password => "Password".to_string(),
             Type::Primary => "Primary".to_string(),
             Type::PrimaryKey => "PrimaryKey".to_string(),
             Type::PrimaryKeyColumn => "PrimaryKeyColumn".to_string(),
             Type::Projection => "Projection".to_string(),
+            Type::Query => "Query".to_string(),
             Type::Reference => "Reference".to_string(),
             Type::Row => "Row".to_string(),
             Type::Scan => "Scan".to_string(),
@@ -363,6 +379,7 @@ impl fmt::Display for Type {
             Type::ShardingColumn => "ShardingColumn".to_string(),
             Type::String => "String".to_string(),
             Type::SingleQuotedString => "SingleQuotedString".to_string(),
+            Type::SqlVdbeMaxSteps => "sql_vdbe_max_steps".to_string(),
             Type::SubQuery => "SubQuery".to_string(),
             Type::Subtract => "Subtract".to_string(),
             Type::Table => "Table".to_string(),
@@ -388,8 +405,9 @@ impl fmt::Display for Type {
             Type::Unsigned => "Unsigned".to_string(),
             Type::Value => "Value".to_string(),
             Type::Values => "Values".to_string(),
-            Type::Vinyl => "Vinyl".to_string(),
             Type::ValuesRow => "ValuesRow".to_string(),
+            Type::Vinyl => "Vinyl".to_string(),
+            Type::VTableMaxRows => "vtable_max_rows".to_string(),
             Type::GroupBy => "GroupBy".to_string(),
             Type::GroupingElement => "GroupingElement".to_string(),
         };
diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest
index 549119b6c3..09ca8c2943 100644
--- a/sbroad-core/src/frontend/sql/query.pest
+++ b/sbroad-core/src/frontend/sql/query.pest
@@ -1,6 +1,16 @@
 Command = _{ SOI ~ (ExplainQuery | Query | DDL | ACL) ~ EOF }
 
-ACL = _{ DropUser }
+ACL = _{ CreateUser | DropUser }
+    CreateUser = {
+        ^"create" ~ ^"user" ~ UserName ~ (^"with")? ~ ^"password" ~ PasswordString ~
+        AuthMethod? ~ Option?
+    }
+        PasswordString = _{ "'" ~ Password ~ "'" }
+            Password = @{ String }
+	AuthMethod = { ^"using" ~ (ChapSha1 | Md5 | Ldap) }
+            ChapSha1 = { ^"chap-sha1" }
+            Md5 = { ^"md5" }
+            Ldap = { ^"ldap" }
     DropUser = { ^"drop" ~ ^"user" ~ UserName ~ Option? }
         UserName = @{ Name }
 
diff --git a/sbroad-core/src/ir/acl.rs b/sbroad-core/src/ir/acl.rs
index 53a702b62b..5aaec8f997 100644
--- a/sbroad-core/src/ir/acl.rs
+++ b/sbroad-core/src/ir/acl.rs
@@ -4,7 +4,16 @@ use tarantool::decimal::Decimal;
 
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub enum Acl {
-    DropUser { name: String, timeout: Decimal },
+    CreateUser {
+        name: String,
+        password: String,
+        auth_method: String,
+        timeout: Decimal,
+    },
+    DropUser {
+        name: String,
+        timeout: Decimal,
+    },
 }
 
 impl Acl {
@@ -14,7 +23,7 @@ impl Acl {
     /// - timeout parsing error
     pub fn timeout(&self) -> Result<f64, SbroadError> {
         match self {
-            Acl::DropUser { ref timeout, .. } => timeout,
+            Acl::CreateUser { ref timeout, .. } | Acl::DropUser { ref timeout, .. } => timeout,
         }
         .to_string()
         .parse()
-- 
GitLab