diff --git a/e2e_test/ddl/privilege.slt b/e2e_test/ddl/privilege.slt new file mode 100644 index 0000000000000..ab66216278d97 --- /dev/null +++ b/e2e_test/ddl/privilege.slt @@ -0,0 +1,83 @@ +# Create a super user. +statement ok +CREATE USER user WITH SUPERUSER PASSWORD 'password'; + +# Create a user. +statement ok +CREATE USER user1 WITH PASSWORD 'password1'; + +# Create a database. +statement ok +CREATE DATABASE db1; + +# Create a schema. +statement ok +CREATE SCHEMA db1.schema1; + +# Grant privilege for user1. +statement ok +GRANT ALL ON DATABASE db1 TO user1 WITH GRANT OPTION GRANTED BY user; + +# Grant invalid privilege on database for user1. +statement error +GRANT INSERT ON DATABASE db1 TO user1 WITH GRANT OPTION GRANTED BY user; + +# Grant privilege on invalid database for user1. +statement error +GRANT ALL ON DATABASE db_invalid TO user1 WITH GRANT OPTION GRANTED BY user; + +# Grant privilege on database for invalid user. +statement error +GRANT ALL ON DATABASE db_invalid TO user_invalid WITH GRANT OPTION GRANTED BY user; + +# Grant privilege on schema for user1. +statement ok +GRANT CREATE ON SCHEMA db1.schema1 TO user1 WITH GRANT OPTION GRANTED BY user; + +# Grant privilege on all sources in schema for user1. +statement ok +GRANT ALL PRIVILEGES ON ALL SOURCES IN SCHEMA db1.schema1 TO user1 GRANTED BY user; + +# Grant privilege on all mviews in schema for user1. +statement ok +GRANT ALL PRIVILEGES ON ALL MATERIALIZED VIEWS IN SCHEMA db1.schema1 TO user1 GRANTED BY user; + +# Revoke privilege on all mviews in schema for user1. +statement ok +REVOKE ALL PRIVILEGES ON ALL MATERIALIZED VIEWS IN SCHEMA db1.schema1 FROM user1; + +# Revoke privilege on all sources in schema for user1. +statement ok +REVOKE ALL PRIVILEGES ON ALL SOURCES IN SCHEMA db1.schema1 FROM user1; + +# Revoke privilege on schema for user1. +statement ok +REVOKE CREATE ON SCHEMA db1.schema1 FROM user1; + +# Revoke GRANT OPTION FOR from database for user1. +statement ok +REVOKE GRANT OPTION FOR ALL ON DATABASE db1 from user1 GRANTED BY user; + +# Revoke privilege on database for user1. +statement ok +REVOKE ALL ON DATABASE db1 FROM user1; + +# Drop schema +statement ok +DROP SCHEMA db1.schema1; + +# Drop schema +statement ok +DROP SCHEMA db1.dev; + +# Drop database +statement ok +DROP DATABASE db1; + +# Drop user1 +statement ok +DROP USER user1; + +# Drop user +statement ok +DROP USER user; diff --git a/proto/user.proto b/proto/user.proto index 0b4af6186b4c2..9158164f1fa0b 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -32,39 +32,7 @@ message UserInfo { /// GrantPrivilege defines a privilege granted to a user. message GrantPrivilege { - message GrantDatabase { - uint32 database_id = 1; - } - - message GrantSchema { - uint32 database_id = 1; - uint32 schema_id = 2; - } - - message GrantTable { - uint32 database_id = 1; - uint32 schema_id = 2; - uint32 table_id = 3; - } - - message GrantSource { - uint32 database_id = 1; - uint32 schema_id = 2; - uint32 source_id = 3; - } - - /// To support grant privilege on ALL TABLES IN SCHEMA schema_name. - message GrantAllTables { - uint32 database_id = 1; - uint32 schema_id = 2; - } - - message GrantAllSources { - uint32 database_id = 1; - uint32 schema_id = 2; - } - - enum Privilege { + enum Action { UNKNOWN = 0; SELECT = 1; INSERT = 2; @@ -74,20 +42,20 @@ message GrantPrivilege { CONNECT = 6; } - message PrivilegeWithGrantOption { - Privilege privilege = 1; + message ActionWithGrantOption { + Action action = 1; bool with_grant_option = 2; } - oneof target { - GrantDatabase grant_database = 1; - GrantSchema grant_schema = 2; - GrantTable grant_table = 3; - GrantSource grant_source = 4; - GrantAllTables grant_all_tables = 5; - GrantAllSources grant_all_sources = 6; + oneof object { + uint32 database_id = 1; + uint32 schema_id = 2; + uint32 table_id = 3; + uint32 source_id = 4; + uint32 all_tables_schema_id = 5; + uint32 all_sources_schema_id = 6; } - repeated PrivilegeWithGrantOption privilege_with_opts = 7; + repeated ActionWithGrantOption action_with_opts = 7; } message CreateUserRequest { @@ -109,7 +77,7 @@ message DropUserResponse { } message GrantPrivilegeRequest { - string user_name = 1; + repeated string users = 1; repeated GrantPrivilege privileges = 2; bool with_grant_option = 3; } @@ -120,7 +88,7 @@ message GrantPrivilegeResponse { } message RevokePrivilegeRequest { - string user_name = 1; + repeated string users = 1; repeated GrantPrivilege privileges = 2; bool revoke_grant_option = 3; } diff --git a/src/frontend/src/handler/create_user.rs b/src/frontend/src/handler/create_user.rs index aa6aa5ca64ac9..b154f51c0b7d6 100644 --- a/src/frontend/src/handler/create_user.rs +++ b/src/frontend/src/handler/create_user.rs @@ -24,10 +24,7 @@ use crate::catalog::CatalogError; use crate::session::OptimizerContext; use crate::user::{encrypt_default, try_extract}; -pub(crate) fn make_prost_user_info( - name: ObjectName, - options: &CreateUserWithOptions, -) -> Result { +fn make_prost_user_info(name: ObjectName, options: &CreateUserWithOptions) -> Result { let mut user_info = UserInfo { name: Binder::resolve_user_name(name)?, // the LOGIN option is implied if it is not explicitly specified. diff --git a/src/frontend/src/handler/handle_privilege.rs b/src/frontend/src/handler/handle_privilege.rs new file mode 100644 index 0000000000000..9cd83f386b694 --- /dev/null +++ b/src/frontend/src/handler/handle_privilege.rs @@ -0,0 +1,338 @@ +// Copyright 2022 Singularity Data +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::error::{ErrorCode, Result}; +use risingwave_pb::user::grant_privilege::{ + Action as ProstAction, ActionWithGrantOption, Object as ProstObject, +}; +use risingwave_pb::user::GrantPrivilege as ProstPrivilege; +use risingwave_sqlparser::ast::{Action, GrantObjects, Privileges, Statement}; + +use crate::binder::Binder; +use crate::session::{OptimizerContext, SessionImpl}; + +// TODO: add user_privilege mod under user manager and move check and expand logic there, and bitmap +// impl for privilege check. +static AVAILABLE_ACTION_ON_DATABASE: &[Action] = &[Action::Connect, Action::Create]; +static AVAILABLE_ACTION_ON_SCHEMA: &[Action] = &[Action::Create]; +static AVAILABLE_ACTION_ON_SOURCE: &[Action] = &[ + Action::Select { columns: None }, + Action::Update { columns: None }, + Action::Insert { columns: None }, + Action::Delete, +]; +static AVAILABLE_ACTION_ON_MVIEW: &[Action] = &[Action::Select { columns: None }]; + +pub(crate) fn check_privilege_type(privilege: &Privileges, objects: &GrantObjects) -> Result<()> { + match privilege { + Privileges::All { .. } => Ok(()), + Privileges::Actions(actions) => { + let valid = match objects { + GrantObjects::Databases(_) => actions + .iter() + .all(|action| AVAILABLE_ACTION_ON_DATABASE.contains(action)), + GrantObjects::Schemas(_) => actions + .iter() + .all(|action| AVAILABLE_ACTION_ON_SCHEMA.contains(action)), + GrantObjects::Sources(_) | GrantObjects::AllSourcesInSchema { .. } => actions + .iter() + .all(|action| AVAILABLE_ACTION_ON_SOURCE.contains(action)), + GrantObjects::Mviews(_) | GrantObjects::AllMviewsInSchema { .. } => actions + .iter() + .all(|action| AVAILABLE_ACTION_ON_MVIEW.contains(action)), + _ => true, + }; + if !valid { + return Err(ErrorCode::BindError( + "Invalid privilege type for the given object.".to_string(), + ) + .into()); + } + + Ok(()) + } + } +} + +pub(crate) fn available_privilege_actions(objects: &GrantObjects) -> Result> { + match objects { + GrantObjects::Databases(_) => Ok(AVAILABLE_ACTION_ON_DATABASE.to_vec()), + GrantObjects::Schemas(_) => Ok(AVAILABLE_ACTION_ON_SCHEMA.to_vec()), + GrantObjects::Sources(_) | GrantObjects::AllSourcesInSchema { .. } => { + Ok(AVAILABLE_ACTION_ON_SOURCE.to_vec()) + } + GrantObjects::Mviews(_) | GrantObjects::AllMviewsInSchema { .. } => { + Ok(AVAILABLE_ACTION_ON_MVIEW.to_vec()) + } + _ => Err( + ErrorCode::BindError("Invalid privilege type for the given object.".to_string()).into(), + ), + } +} + +fn make_prost_privilege( + session: &SessionImpl, + privileges: Privileges, + objects: GrantObjects, +) -> Result> { + check_privilege_type(&privileges, &objects)?; + + let catalog_reader = session.env().catalog_reader(); + let reader = catalog_reader.read_guard(); + let actions = match privileges { + Privileges::All { .. } => available_privilege_actions(&objects)?, + Privileges::Actions(actions) => actions, + }; + let mut grant_objs = vec![]; + match objects { + GrantObjects::Databases(databases) => { + for db in databases { + let database_name = Binder::resolve_database_name(db)?; + let database = reader.get_database_by_name(&database_name)?; + grant_objs.push(ProstObject::DatabaseId(database.id())); + } + } + GrantObjects::Schemas(schemas) => { + for schema in schemas { + let (database_name, schema_name) = + Binder::resolve_schema_name(session.database(), schema)?; + let schema = reader.get_schema_by_name(&database_name, &schema_name)?; + grant_objs.push(ProstObject::SchemaId(schema.id())); + } + } + GrantObjects::Mviews(tables) => { + for name in tables { + let (schema_name, table_name) = Binder::resolve_table_name(name)?; + let table = + reader.get_table_by_name(session.database(), &schema_name, &table_name)?; + grant_objs.push(ProstObject::TableId(table.id().table_id)); + } + } + GrantObjects::Sources(sources) => { + for name in sources { + let (schema_name, table_name) = Binder::resolve_table_name(name)?; + let source = + reader.get_source_by_name(session.database(), &schema_name, &table_name)?; + grant_objs.push(ProstObject::SourceId(source.id)); + } + } + GrantObjects::AllSourcesInSchema { schemas } => { + for schema in schemas { + let (database_name, schema_name) = + Binder::resolve_schema_name(session.database(), schema)?; + let schema = reader.get_schema_by_name(&database_name, &schema_name)?; + grant_objs.push(ProstObject::AllSourcesSchemaId(schema.id())); + } + } + GrantObjects::AllMviewsInSchema { schemas } => { + for schema in schemas { + let (database_name, schema_name) = + Binder::resolve_schema_name(session.database(), schema)?; + let schema = reader.get_schema_by_name(&database_name, &schema_name)?; + grant_objs.push(ProstObject::AllTablesSchemaId(schema.id())); + } + } + _ => { + return Err(ErrorCode::BindError( + "GRANT statement does not support this object type".to_string(), + ) + .into()); + } + }; + let action_with_opts = actions + .iter() + .map(|action| { + let prost_action = match action { + Action::Select { .. } => ProstAction::Select, + Action::Insert { .. } => ProstAction::Insert, + Action::Update { .. } => ProstAction::Update, + Action::Delete { .. } => ProstAction::Delete, + Action::Connect => ProstAction::Connect, + Action::Create => ProstAction::Create, + _ => unreachable!(), + }; + ActionWithGrantOption { + action: prost_action as i32, + ..Default::default() + } + }) + .collect::>(); + + let mut prost_privileges = vec![]; + for objs in grant_objs { + prost_privileges.push(ProstPrivilege { + action_with_opts: action_with_opts.clone(), + object: Some(objs), + }); + } + Ok(prost_privileges) +} + +pub async fn handle_grant_privilege( + context: OptimizerContext, + stmt: Statement, +) -> Result { + let session = context.session_ctx; + let Statement::Grant { + privileges, + objects, + grantees, + with_grant_option, + granted_by, + } = stmt else { return Err(ErrorCode::BindError("Invalid grant statement".to_string()).into()); }; + let users = grantees.into_iter().map(|g| g.value).collect::>(); + { + let user_reader = session.env().user_info_reader(); + let reader = user_reader.read_guard(); + if users + .iter() + .any(|user| reader.get_user_by_name(user).is_none()) + { + return Err(ErrorCode::BindError("Grantee does not exist".to_string()).into()); + } + if let Some(granted_by) = granted_by { + if reader.get_user_by_name(&granted_by.value).is_none() { + return Err(ErrorCode::BindError("Grantor does not exist".to_string()).into()); + } + // TODO: check whether if grantor is a super user or have the privilege to grant. + } + } + + let privileges = make_prost_privilege(&session, privileges, objects)?; + let user_info_writer = session.env().user_info_writer(); + user_info_writer + .grant_privilege(users, privileges, with_grant_option) + .await?; + Ok(PgResponse::empty_result(StatementType::GRANT_PRIVILEGE)) +} + +pub async fn handle_revoke_privilege( + context: OptimizerContext, + stmt: Statement, +) -> Result { + let session = context.session_ctx; + let Statement::Revoke { + privileges, + objects, + grantees, + granted_by, + revoke_grant_option, + cascade: _, + } = stmt else { return Err(ErrorCode::BindError("Invalid revoke statement".to_string()).into()); }; + // TODO: support cascade and restrict option, this requires to record granted_by in each + // actions. + let users = grantees.into_iter().map(|g| g.value).collect::>(); + { + let user_reader = session.env().user_info_reader(); + let reader = user_reader.read_guard(); + if users + .iter() + .any(|user| reader.get_user_by_name(user).is_none()) + { + return Err(ErrorCode::BindError("Grantee does not exist".to_string()).into()); + } + if let Some(granted_by) = granted_by { + if reader.get_user_by_name(&granted_by.value).is_none() { + return Err(ErrorCode::BindError("Grantor does not exist".to_string()).into()); + } + // TODO: check whether if grantor is a super user or have the privilege to grant. + } + } + + let privileges = make_prost_privilege(&session, privileges, objects)?; + let user_info_writer = session.env().user_info_writer(); + user_info_writer + .revoke_privilege(users, privileges, revoke_grant_option) + .await?; + Ok(PgResponse::empty_result(StatementType::REVOKE_PRIVILEGE)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::LocalFrontend; + + #[tokio::test] + async fn test_grant_privilege() { + let frontend = LocalFrontend::new(Default::default()).await; + let session = frontend.session_ref(); + frontend + .run_sql("CREATE USER user WITH SUPERUSER PASSWORD 'password'") + .await + .unwrap(); + frontend + .run_sql("CREATE USER user1 WITH PASSWORD 'password1'") + .await + .unwrap(); + frontend.run_sql("CREATE DATABASE db1").await.unwrap(); + frontend + .run_sql("GRANT ALL ON DATABASE db1 TO user1 WITH GRANT OPTION GRANTED BY user") + .await + .unwrap(); + + let database_id = { + let catalog_reader = session.env().catalog_reader(); + let reader = catalog_reader.read_guard(); + reader.get_database_by_name("db1").unwrap().id() + }; + + { + let user_reader = session.env().user_info_reader(); + let reader = user_reader.read_guard(); + let user_info = reader.get_user_by_name("user1").unwrap(); + assert_eq!( + user_info.grant_privileges, + vec![ProstPrivilege { + action_with_opts: vec![ + ActionWithGrantOption { + action: ProstAction::Connect as i32, + with_grant_option: true + }, + ActionWithGrantOption { + action: ProstAction::Create as i32, + with_grant_option: true + } + ], + object: Some(ProstObject::DatabaseId(database_id)) + }] + ); + } + + frontend + .run_sql("REVOKE GRANT OPTION FOR ALL ON DATABASE db1 from user1 GRANTED BY user") + .await + .unwrap(); + { + let user_reader = session.env().user_info_reader(); + let reader = user_reader.read_guard(); + let user_info = reader.get_user_by_name("user1").unwrap(); + assert!(user_info + .grant_privileges + .iter() + .all(|p| p.action_with_opts.iter().all(|ao| !ao.with_grant_option))); + } + + frontend + .run_sql("REVOKE ALL ON DATABASE db1 from user1 GRANTED BY user") + .await + .unwrap(); + { + let user_reader = session.env().user_info_reader(); + let reader = user_reader.read_guard(); + let user_info = reader.get_user_by_name("user1").unwrap(); + assert!(user_info.grant_privileges.is_empty()); + } + } +} diff --git a/src/frontend/src/handler/mod.rs b/src/frontend/src/handler/mod.rs index 98b52e248ea07..310fe5b6ba4a6 100644 --- a/src/frontend/src/handler/mod.rs +++ b/src/frontend/src/handler/mod.rs @@ -39,6 +39,7 @@ pub mod drop_table; pub mod drop_user; mod explain; mod flush; +pub mod handle_privilege; #[allow(dead_code)] pub mod query; mod set; @@ -69,6 +70,8 @@ pub(super) async fn handle(session: Arc, stmt: Statement) -> Result .. } => create_schema::handle_create_schema(context, schema_name, if_not_exists).await, Statement::CreateUser(stmt) => create_user::handle_create_user(context, stmt).await, + Statement::Grant { .. } => handle_privilege::handle_grant_privilege(context, stmt).await, + Statement::Revoke { .. } => handle_privilege::handle_revoke_privilege(context, stmt).await, Statement::Describe { name } => describe::handle_describe(context, name).await, Statement::ShowObjects(show_object) => show::handle_show_object(context, show_object).await, Statement::Drop(DropStatement { diff --git a/src/frontend/src/test_utils.rs b/src/frontend/src/test_utils.rs index 4c5bf242cc32c..cdeef98420bd7 100644 --- a/src/frontend/src/test_utils.rs +++ b/src/frontend/src/test_utils.rs @@ -46,6 +46,7 @@ use crate::planner::Planner; use crate::session::{FrontendEnv, OptimizerContext, SessionImpl}; use crate::user::user_manager::UserInfoManager; use crate::user::user_service::UserInfoWriter; +use crate::user::UserName; use crate::FrontendOpts; /// An embedded frontend without starting meta and without starting frontend as a tcp server. @@ -302,21 +303,23 @@ impl UserInfoWriter for MockUserInfoWriter { /// `GrantAllSources` when grant privilege to user. async fn grant_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, with_grant_option: bool, ) -> Result<()> { let privileges = privileges .into_iter() .map(|mut p| { - p.privilege_with_opts + p.action_with_opts .iter_mut() - .for_each(|po| po.with_grant_option = with_grant_option); + .for_each(|ao| ao.with_grant_option = with_grant_option); p }) .collect::>(); - if let Some(u) = self.user_info.write().get_user_mut(user_name) { - u.grant_privileges.extend(privileges); + for user_name in users { + if let Some(u) = self.user_info.write().get_user_mut(&user_name) { + u.grant_privileges.extend(privileges.clone()); + } } Ok(()) } @@ -325,35 +328,39 @@ impl UserInfoWriter for MockUserInfoWriter { /// `RevokeAllSources` when revoke privilege from user. async fn revoke_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, revoke_grant_option: bool, ) -> Result<()> { - if let Some(u) = self.user_info.write().get_user_mut(user_name) { - u.grant_privileges.iter_mut().for_each(|p| { - for rp in &privileges { - if rp.target != p.target { - continue; - } - if revoke_grant_option { - for po in &mut p.privilege_with_opts { - if rp - .privilege_with_opts - .iter() - .any(|rpo| rpo.privilege == po.privilege) - { - po.with_grant_option = false; + for user_name in users { + if let Some(u) = self.user_info.write().get_user_mut(&user_name) { + u.grant_privileges.iter_mut().for_each(|p| { + for rp in &privileges { + if rp.object != p.object { + continue; + } + if revoke_grant_option { + for ao in &mut p.action_with_opts { + if rp + .action_with_opts + .iter() + .any(|rao| rao.action == ao.action) + { + ao.with_grant_option = false; + } } + } else { + p.action_with_opts.retain(|po| { + rp.action_with_opts + .iter() + .all(|rao| rao.action != po.action) + }); } - } else { - p.privilege_with_opts.retain(|po| { - rp.privilege_with_opts - .iter() - .all(|rpo| rpo.privilege != po.privilege) - }); } - } - }); + }); + u.grant_privileges + .retain(|p| !p.action_with_opts.is_empty()); + } } Ok(()) } diff --git a/src/frontend/src/user/user_manager.rs b/src/frontend/src/user/user_manager.rs index 2eb4a8c4d8679..d9fdc87dba7e0 100644 --- a/src/frontend/src/user/user_manager.rs +++ b/src/frontend/src/user/user_manager.rs @@ -57,11 +57,11 @@ impl UserInfoManager { self.users.insert(user_info.name.clone(), user_info); } - pub fn verify(&mut self, _user_name: &str, _password: &str) -> bool { + pub fn authorize(&mut self, _user_name: &str, _password: &str) -> bool { todo!() } - pub fn authorize(&self, _user_name: &str, _privileges: &[GrantPrivilege]) -> bool { + pub fn verify(&self, _user_name: &str, _privileges: &[GrantPrivilege]) -> bool { todo!() } diff --git a/src/frontend/src/user/user_service.rs b/src/frontend/src/user/user_service.rs index e4ba65699d028..71185c7fef642 100644 --- a/src/frontend/src/user/user_service.rs +++ b/src/frontend/src/user/user_service.rs @@ -23,7 +23,7 @@ use risingwave_rpc_client::MetaClient; use tokio::sync::watch::Receiver; use crate::user::user_manager::UserInfoManager; -use crate::user::UserInfoVersion; +use crate::user::{UserInfoVersion, UserName}; pub type UserInfoReadGuard = ArcRwLockReadGuard; @@ -47,14 +47,14 @@ pub trait UserInfoWriter: Send + Sync { async fn grant_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, with_grant_option: bool, ) -> Result<()>; async fn revoke_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, revoke_grant_option: bool, ) -> Result<()>; @@ -80,26 +80,26 @@ impl UserInfoWriter for UserInfoWriterImpl { async fn grant_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, with_grant_option: bool, ) -> Result<()> { let version = self .meta_client - .grant_privilege(user_name, privileges, with_grant_option) + .grant_privilege(users, privileges, with_grant_option) .await?; self.wait_version(version).await } async fn revoke_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, revoke_grant_option: bool, ) -> Result<()> { let version = self .meta_client - .revoke_privilege(user_name, privileges, revoke_grant_option) + .revoke_privilege(users, privileges, revoke_grant_option) .await?; self.wait_version(version).await } diff --git a/src/meta/src/manager/catalog.rs b/src/meta/src/manager/catalog.rs index 13a6750b72789..d6666ef84e3eb 100644 --- a/src/meta/src/manager/catalog.rs +++ b/src/meta/src/manager/catalog.rs @@ -577,30 +577,22 @@ where } } - pub async fn list_tables( - &self, - database_id: DatabaseId, - schema_id: SchemaId, - ) -> Result> { + pub async fn list_tables(&self, schema_id: SchemaId) -> Result> { let core = self.core.lock().await; let tables = Table::list(core.env.meta_store()).await?; Ok(tables .iter() - .filter(|t| t.database_id == database_id && t.schema_id == schema_id) + .filter(|t| t.schema_id == schema_id) .map(|t| t.id) .collect()) } - pub async fn list_sources( - &self, - database_id: DatabaseId, - schema_id: SchemaId, - ) -> Result> { + pub async fn list_sources(&self, schema_id: SchemaId) -> Result> { let core = self.core.lock().await; let sources = Source::list(core.env.meta_store()).await?; Ok(sources .iter() - .filter(|s| s.database_id == database_id && s.schema_id == schema_id) + .filter(|s| s.schema_id == schema_id) .map(|s| s.id) .collect()) } diff --git a/src/meta/src/manager/user.rs b/src/meta/src/manager/user.rs index 3cd311aa2a78e..841180f0a158c 100644 --- a/src/meta/src/manager/user.rs +++ b/src/meta/src/manager/user.rs @@ -20,7 +20,7 @@ use risingwave_common::error::ErrorCode::InternalError; use risingwave_common::error::{Result, RwError}; use risingwave_pb::meta::subscribe_response::{Info, Operation}; use risingwave_pb::user::auth_info::EncryptionType; -use risingwave_pb::user::grant_privilege::{PrivilegeWithGrantOption, Target}; +use risingwave_pb::user::grant_privilege::{ActionWithGrantOption, Object}; use risingwave_pb::user::{AuthInfo, GrantPrivilege, UserInfo}; use tokio::sync::{Mutex, MutexGuard}; @@ -153,25 +153,25 @@ impl UserManager { // Merge new granted privilege. #[inline(always)] fn merge_privilege(origin_privilege: &mut GrantPrivilege, new_privilege: &GrantPrivilege) { - assert_eq!(origin_privilege.target, new_privilege.target); + assert_eq!(origin_privilege.object, new_privilege.object); - let mut privilege_map = HashMap::::from_iter( + let mut action_map = HashMap::::from_iter( origin_privilege - .privilege_with_opts + .action_with_opts .iter() - .map(|po| (po.privilege, po.with_grant_option)), + .map(|ao| (ao.action, ao.with_grant_option)), ); - for npo in &new_privilege.privilege_with_opts { - if let Some(po) = privilege_map.get_mut(&npo.privilege) { - *po |= npo.with_grant_option; + for nao in &new_privilege.action_with_opts { + if let Some(o) = action_map.get_mut(&nao.action) { + *o |= nao.with_grant_option; } else { - privilege_map.insert(npo.privilege, npo.with_grant_option); + action_map.insert(nao.action, nao.with_grant_option); } } - origin_privilege.privilege_with_opts = privilege_map + origin_privilege.action_with_opts = action_map .into_iter() - .map(|(privilege, with_grant_option)| PrivilegeWithGrantOption { - privilege, + .map(|(action, with_grant_option)| ActionWithGrantOption { + action, with_grant_option, }) .collect(); @@ -179,132 +179,149 @@ impl UserManager { pub async fn grant_privilege( &self, - user_name: &UserName, + users: &[UserName], new_grant_privileges: &[GrantPrivilege], ) -> Result { let mut core = self.core.lock().await; - let mut user = core - .get(user_name) - .ok_or_else(|| InternalError(format!("User {} does not exist", user_name))) - .cloned()?; + let mut transaction = Transaction::default(); + let mut user_updated = Vec::with_capacity(users.len()); + for user_name in users { + let mut user = core + .get(user_name) + .ok_or_else(|| InternalError(format!("User {} does not exist", user_name))) + .cloned()?; + + if user.is_supper { + return Err(RwError::from(InternalError(format!( + "Cannot grant privilege to supper user {}", + user_name + )))); + } - if user.is_supper { - return Err(RwError::from(InternalError(format!( - "Cannot grant privilege to supper user {}", - user_name - )))); + new_grant_privileges.iter().for_each(|new_grant_privilege| { + if let Some(privilege) = user + .grant_privileges + .iter_mut() + .find(|p| p.object == new_grant_privilege.object) + { + Self::merge_privilege(privilege, new_grant_privilege); + } else { + user.grant_privileges.push(new_grant_privilege.clone()); + } + }); + user.upsert_in_transaction(&mut transaction)?; + user_updated.push(user); } - new_grant_privileges.iter().for_each(|new_grant_privilege| { - if let Some(privilege) = user - .grant_privileges - .iter_mut() - .find(|p| p.target == new_grant_privilege.target) - { - Self::merge_privilege(privilege, new_grant_privilege); - } else { - user.grant_privileges.push(new_grant_privilege.clone()); - } - }); + self.env.meta_store().txn(transaction).await?; + let mut version = 0; + for user in user_updated { + core.insert(user.name.clone(), user.clone()); + version = self + .env + .notification_manager() + .notify_frontend(Operation::Update, Info::User(user)) + .await; + } - user.insert(self.env.meta_store()).await?; - core.insert(user_name.clone(), user.clone()); - let version = self - .env - .notification_manager() - .notify_frontend(Operation::Update, Info::User(user)) - .await; Ok(version) } - // Revoke privilege from target. + // Revoke privilege from object. #[inline(always)] fn revoke_privilege_inner( origin_privilege: &mut GrantPrivilege, revoke_grant_privilege: &GrantPrivilege, revoke_grant_option: bool, ) { - assert_eq!(origin_privilege.target, revoke_grant_privilege.target); + assert_eq!(origin_privilege.object, revoke_grant_privilege.object); if revoke_grant_option { // Only revoke with grant option. - origin_privilege - .privilege_with_opts - .iter_mut() - .for_each(|po| { - if revoke_grant_privilege - .privilege_with_opts - .iter() - .any(|ro| ro.privilege == po.privilege) - { - po.with_grant_option = false; - } - }) + origin_privilege.action_with_opts.iter_mut().for_each(|ao| { + if revoke_grant_privilege + .action_with_opts + .iter() + .any(|ro| ro.action == ao.action) + { + ao.with_grant_option = false; + } + }) } else { // Revoke all privileges matched with revoke_grant_privilege. - origin_privilege.privilege_with_opts.retain(|po| { + origin_privilege.action_with_opts.retain(|ao| { !revoke_grant_privilege - .privilege_with_opts + .action_with_opts .iter() - .any(|ro| ro.privilege == po.privilege) + .any(|rao| rao.action == ao.action) }); } } pub async fn revoke_privilege( &self, - user_name: &UserName, + users: &[UserName], revoke_grant_privileges: &[GrantPrivilege], revoke_grant_option: bool, ) -> Result { let mut core = self.core.lock().await; - let mut user = core - .get(user_name) - .ok_or_else(|| InternalError(format!("User {} does not exist", user_name))) - .cloned()?; - - if user.is_supper { - return Err(RwError::from(InternalError(format!( - "Cannot revoke privilege from supper user {}", - user_name - )))); - } + let mut transaction = Transaction::default(); + let mut user_updated = Vec::with_capacity(users.len()); + for user_name in users { + let mut user = core + .get(user_name) + .ok_or_else(|| InternalError(format!("User {} does not exist", user_name))) + .cloned()?; + + if user.is_supper { + return Err(RwError::from(InternalError(format!( + "Cannot revoke privilege from supper user {}", + user_name + )))); + } - let mut empty_privilege = false; - revoke_grant_privileges - .iter() - .for_each(|revoke_grant_privilege| { - for privilege in &mut user.grant_privileges { - if privilege.target == revoke_grant_privilege.target { - Self::revoke_privilege_inner( - privilege, - revoke_grant_privilege, - revoke_grant_option, - ); - empty_privilege |= privilege.privilege_with_opts.is_empty(); - break; + let mut empty_privilege = false; + revoke_grant_privileges + .iter() + .for_each(|revoke_grant_privilege| { + for privilege in &mut user.grant_privileges { + if privilege.object == revoke_grant_privilege.object { + Self::revoke_privilege_inner( + privilege, + revoke_grant_privilege, + revoke_grant_option, + ); + empty_privilege |= privilege.action_with_opts.is_empty(); + break; + } } - } - }); + }); - if empty_privilege { - user.grant_privileges - .retain(|privilege| !privilege.privilege_with_opts.is_empty()); + if empty_privilege { + user.grant_privileges + .retain(|privilege| !privilege.action_with_opts.is_empty()); + } + user.upsert_in_transaction(&mut transaction)?; + user_updated.push(user); + } + + self.env.meta_store().txn(transaction).await?; + let mut version = 0; + for user in user_updated { + core.insert(user.name.clone(), user.clone()); + version = self + .env + .notification_manager() + .notify_frontend(Operation::Update, Info::User(user)) + .await; } - user.insert(self.env.meta_store()).await?; - core.insert(user_name.clone(), user.clone()); - let version = self - .env - .notification_manager() - .notify_frontend(Operation::Update, Info::User(user)) - .await; Ok(version) } - /// `release_privileges` removes the privileges with given target from all users, it will be + /// `release_privileges` removes the privileges with given object from all users, it will be /// called when a database/schema/table/source is dropped. - pub async fn release_privileges(&self, target: &Target) -> Result<()> { + pub async fn release_privileges(&self, object: &Object) -> Result<()> { let mut core = self.core.lock().await; let mut transaction = Transaction::default(); let mut users_need_update = vec![]; @@ -312,12 +329,13 @@ impl UserManager { let cnt = user.grant_privileges.len(); let mut user = user.clone(); user.grant_privileges - .retain(|p| p.target.as_ref().unwrap() != target); + .retain(|p| p.object.as_ref().unwrap() != object); if cnt != user.grant_privileges.len() { user.upsert_in_transaction(&mut transaction)?; users_need_update.push(user); } } + self.env.meta_store().txn(transaction).await?; for user in users_need_update { core.insert(user.name.clone(), user.clone()); @@ -333,7 +351,7 @@ impl UserManager { #[cfg(test)] mod tests { - use risingwave_pb::user::grant_privilege::{GrantTable, Privilege}; + use risingwave_pb::user::grant_privilege::Action; use super::*; @@ -345,16 +363,16 @@ mod tests { } fn make_privilege( - target: Target, - privileges: &[Privilege], + object: Object, + actions: &[Action], with_grant_option: bool, ) -> GrantPrivilege { GrantPrivilege { - target: Some(target), - privilege_with_opts: privileges + object: Some(object), + action_with_opts: actions .iter() - .map(|&p| PrivilegeWithGrantOption { - privilege: p as i32, + .map(|&action| ActionWithGrantOption { + action: action as i32, with_grant_option, }) .collect(), @@ -374,82 +392,78 @@ mod tests { let users = user_manager.list_users().await?; assert_eq!(users.len(), 2); - let target = Target::GrantTable(GrantTable { - database_id: 0, - schema_id: 0, - table_id: 0, - }); + let object = Object::TableId(0); // Grant Select/Insert without grant option. user_manager .grant_privilege( - &test_user.to_string(), + &[test_user.to_string()], &[make_privilege( - target.clone(), - &[Privilege::Select, Privilege::Insert], + object.clone(), + &[Action::Select, Action::Insert], false, )], ) .await?; let user = user_manager.get_user(&test_user.to_string()).await?; assert_eq!(user.grant_privileges.len(), 1); - assert_eq!(user.grant_privileges[0].target, Some(target.clone())); - assert_eq!(user.grant_privileges[0].privilege_with_opts.len(), 2); + assert_eq!(user.grant_privileges[0].object, Some(object.clone())); + assert_eq!(user.grant_privileges[0].action_with_opts.len(), 2); assert!(user.grant_privileges[0] - .privilege_with_opts + .action_with_opts .iter() .all(|p| !p.with_grant_option)); // Grant Select/Insert with grant option. user_manager .grant_privilege( - &test_user.to_string(), + &[test_user.to_string()], &[make_privilege( - target.clone(), - &[Privilege::Select, Privilege::Insert], + object.clone(), + &[Action::Select, Action::Insert], true, )], ) .await?; let user = user_manager.get_user(&test_user.to_string()).await?; assert_eq!(user.grant_privileges.len(), 1); - assert_eq!(user.grant_privileges[0].target, Some(target.clone())); - assert_eq!(user.grant_privileges[0].privilege_with_opts.len(), 2); + assert_eq!(user.grant_privileges[0].object, Some(object.clone())); + assert_eq!(user.grant_privileges[0].action_with_opts.len(), 2); assert!(user.grant_privileges[0] - .privilege_with_opts + .action_with_opts .iter() .all(|p| p.with_grant_option)); // Grant Select/Update/Delete with grant option, while Select is duplicated. user_manager .grant_privilege( - &test_user.to_string(), + &[test_user.to_string()], &[make_privilege( - target.clone(), - &[Privilege::Select, Privilege::Update, Privilege::Delete], + object.clone(), + &[Action::Select, Action::Update, Action::Delete], true, )], ) .await?; let user = user_manager.get_user(&test_user.to_string()).await?; assert_eq!(user.grant_privileges.len(), 1); - assert_eq!(user.grant_privileges[0].target, Some(target.clone())); - assert_eq!(user.grant_privileges[0].privilege_with_opts.len(), 4); + assert_eq!(user.grant_privileges[0].object, Some(object.clone())); + assert_eq!(user.grant_privileges[0].action_with_opts.len(), 4); assert!(user.grant_privileges[0] - .privilege_with_opts + .action_with_opts .iter() .all(|p| p.with_grant_option)); // Revoke Select/Update/Delete/Insert with grant option. user_manager .revoke_privilege( - &test_user.to_string(), + &[test_user.to_string()], &[make_privilege( - target.clone(), + object.clone(), &[ - Privilege::Select, - Privilege::Insert, - Privilege::Delete, - Privilege::Update, + Action::Select, + Action::Insert, + Action::Delete, + Action::Update, ], false, )], @@ -457,19 +471,19 @@ mod tests { ) .await?; let user = user_manager.get_user(&test_user.to_string()).await?; - assert_eq!(user.grant_privileges[0].privilege_with_opts.len(), 4); + assert_eq!(user.grant_privileges[0].action_with_opts.len(), 4); assert!(user.grant_privileges[0] - .privilege_with_opts + .action_with_opts .iter() .all(|p| !p.with_grant_option)); // Revoke Select/Delete/Insert. user_manager .revoke_privilege( - &test_user.to_string(), + &[test_user.to_string()], &[make_privilege( - target.clone(), - &[Privilege::Select, Privilege::Insert, Privilege::Delete], + object.clone(), + &[Action::Select, Action::Insert, Action::Delete], false, )], false, @@ -477,10 +491,10 @@ mod tests { .await?; let user = user_manager.get_user(&test_user.to_string()).await?; assert_eq!(user.grant_privileges.len(), 1); - assert_eq!(user.grant_privileges[0].privilege_with_opts.len(), 1); + assert_eq!(user.grant_privileges[0].action_with_opts.len(), 1); - // Release all privileges with target. - user_manager.release_privileges(&target).await?; + // Release all privileges with object. + user_manager.release_privileges(&object).await?; let user = user_manager.get_user(&test_user.to_string()).await?; assert!(user.grant_privileges.is_empty()); diff --git a/src/meta/src/rpc/service/user_service.rs b/src/meta/src/rpc/service/user_service.rs index 3ac0eab1d2fe8..4c979a0c415ca 100644 --- a/src/meta/src/rpc/service/user_service.rs +++ b/src/meta/src/rpc/service/user_service.rs @@ -13,7 +13,7 @@ // limitations under the License. use risingwave_common::error::{tonic_err, Result as RwResult}; -use risingwave_pb::user::grant_privilege::{GrantSource, GrantTable, Target}; +use risingwave_pb::user::grant_privilege::Object; use risingwave_pb::user::user_service_server::UserService; use risingwave_pb::user::{ CreateUserRequest, CreateUserResponse, DropUserRequest, DropUserResponse, GrantPrivilege, @@ -42,7 +42,7 @@ where } } - /// Expands `GrantPrivilege` with target `GrantAllTables` or `GrantAllSources` to specific + /// Expands `GrantPrivilege` with object `GrantAllTables` or `GrantAllSources` to specific /// tables and sources, and set `with_grant_option` inside when grant privilege to a user. async fn expand_privilege( &self, @@ -51,40 +51,26 @@ where ) -> RwResult> { let mut expanded_privileges = Vec::new(); for privilege in privileges { - if let Some(Target::GrantAllTables(target)) = &privilege.target { - let tables = self - .catalog_manager - .list_tables(target.database_id, target.schema_id) - .await?; + if let Some(Object::AllTablesSchemaId(schema_id)) = &privilege.object { + let tables = self.catalog_manager.list_tables(*schema_id).await?; for table_id in tables { let mut privilege = privilege.clone(); - privilege.target = Some(Target::GrantTable(GrantTable { - database_id: target.database_id, - schema_id: target.schema_id, - table_id, - })); + privilege.object = Some(Object::TableId(table_id)); if let Some(true) = with_grant_option { privilege - .privilege_with_opts + .action_with_opts .iter_mut() .for_each(|p| p.with_grant_option = true); } expanded_privileges.push(privilege); } - } else if let Some(Target::GrantAllSources(target)) = &privilege.target { - let sources = self - .catalog_manager - .list_sources(target.database_id, target.schema_id) - .await?; + } else if let Some(Object::AllSourcesSchemaId(source_id)) = &privilege.object { + let sources = self.catalog_manager.list_sources(*source_id).await?; for source_id in sources { let mut privilege = privilege.clone(); - privilege.target = Some(Target::GrantSource(GrantSource { - database_id: target.database_id, - schema_id: target.schema_id, - source_id, - })); + privilege.object = Some(Object::SourceId(source_id)); if let Some(with_grant_option) = with_grant_option { - privilege.privilege_with_opts.iter_mut().for_each(|p| { + privilege.action_with_opts.iter_mut().for_each(|p| { p.with_grant_option = with_grant_option; }); } @@ -93,7 +79,7 @@ where } else { let mut privilege = privilege.clone(); if let Some(with_grant_option) = with_grant_option { - privilege.privilege_with_opts.iter_mut().for_each(|p| { + privilege.action_with_opts.iter_mut().for_each(|p| { p.with_grant_option = with_grant_option; }); } @@ -151,14 +137,13 @@ impl UserService for UserServiceImpl { request: Request, ) -> Result, Status> { let req = request.into_inner(); - let user_name = req.get_user_name(); let new_privileges = self .expand_privilege(req.get_privileges(), Some(req.with_grant_option)) .await .map_err(tonic_err)?; let version = self .user_manager - .grant_privilege(user_name, &new_privileges) + .grant_privilege(&req.users, &new_privileges) .await .map_err(tonic_err)?; @@ -174,7 +159,6 @@ impl UserService for UserServiceImpl { request: Request, ) -> Result, Status> { let req = request.into_inner(); - let user_name = req.get_user_name(); let privileges = self .expand_privilege(req.get_privileges(), None) .await @@ -182,7 +166,7 @@ impl UserService for UserServiceImpl { let revoke_grant_option = req.revoke_grant_option; let version = self .user_manager - .revoke_privilege(user_name, &privileges, revoke_grant_option) + .revoke_privilege(&req.users, &privileges, revoke_grant_option) .await .map_err(tonic_err)?; diff --git a/src/rpc_client/src/meta_client.rs b/src/rpc_client/src/meta_client.rs index e1575d1f32197..0ad8820748f8d 100644 --- a/src/rpc_client/src/meta_client.rs +++ b/src/rpc_client/src/meta_client.rs @@ -255,12 +255,12 @@ impl MetaClient { pub async fn grant_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, with_grant_option: bool, ) -> Result { let request = GrantPrivilegeRequest { - user_name: user_name.to_string(), + users, privileges, with_grant_option, }; @@ -270,12 +270,12 @@ impl MetaClient { pub async fn revoke_privilege( &self, - user_name: &str, + users: Vec, privileges: Vec, revoke_grant_option: bool, ) -> Result { let request = RevokePrivilegeRequest { - user_name: user_name.to_string(), + users, privileges, revoke_grant_option, }; diff --git a/src/sqlparser/src/ast/mod.rs b/src/sqlparser/src/ast/mod.rs index 25dd459b5df88..05d9a56af9263 100644 --- a/src/sqlparser/src/ast/mod.rs +++ b/src/sqlparser/src/ast/mod.rs @@ -828,6 +828,7 @@ pub enum Statement { objects: GrantObjects, grantees: Vec, granted_by: Option, + revoke_grant_option: bool, cascade: bool, }, /// `DEALLOCATE [ PREPARE ] { name | ALL }` @@ -1183,9 +1184,19 @@ impl fmt::Display for Statement { objects, grantees, granted_by, + revoke_grant_option, cascade, } => { - write!(f, "REVOKE {} ", privileges)?; + write!( + f, + "REVOKE {}{} ", + if *revoke_grant_option { + "GRANT OPTION FOR " + } else { + "" + }, + privileges + )?; write!(f, "ON {} ", objects)?; write!(f, "FROM {}", display_comma_separated(grantees))?; if let Some(grantor) = granted_by { @@ -1353,8 +1364,18 @@ pub enum GrantObjects { AllSequencesInSchema { schemas: Vec }, /// Grant privileges on `ALL TABLES IN SCHEMA [, ...]` AllTablesInSchema { schemas: Vec }, + /// Grant privileges on `ALL SOURCES IN SCHEMA [, ...]` + AllSourcesInSchema { schemas: Vec }, + /// Grant privileges on `ALL MATERIALIZED VIEWS IN SCHEMA [, ...]` + AllMviewsInSchema { schemas: Vec }, + /// Grant privileges on specific databases + Databases(Vec), /// Grant privileges on specific schemas Schemas(Vec), + /// Grant privileges on specific sources + Sources(Vec), + /// Grant privileges on specific materialized views + Mviews(Vec), /// Grant privileges on specific sequences Sequences(Vec), /// Grant privileges on specific tables @@ -1387,6 +1408,29 @@ impl fmt::Display for GrantObjects { display_comma_separated(schemas) ) } + GrantObjects::AllSourcesInSchema { schemas } => { + write!( + f, + "ALL SOURCES IN SCHEMA {}", + display_comma_separated(schemas) + ) + } + GrantObjects::AllMviewsInSchema { schemas } => { + write!( + f, + "ALL MATERIALIZED VIEWS IN SCHEMA {}", + display_comma_separated(schemas) + ) + } + GrantObjects::Databases(databases) => { + write!(f, "DATABASE {}", display_comma_separated(databases)) + } + GrantObjects::Sources(sources) => { + write!(f, "SOURCE {}", display_comma_separated(sources)) + } + GrantObjects::Mviews(mviews) => { + write!(f, "MATERIALIZED VIEW {}", display_comma_separated(mviews)) + } } } } diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index 5577adef8f934..01cbcd2313c86 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -2823,6 +2823,8 @@ impl Parser { self.parse_comma_separated(Parser::parse_grant_permission)? .into_iter() .map(|(kw, columns)| match kw { + Keyword::CONNECT => Action::Connect, + Keyword::CREATE => Action::Create, Keyword::DELETE => Action::Delete, Keyword::INSERT => Action::Insert { columns }, Keyword::REFERENCES => Action::References { columns }, @@ -2857,13 +2859,41 @@ impl Parser { GrantObjects::AllSequencesInSchema { schemas: self.parse_comma_separated(Parser::parse_object_name)?, } + } else if self.parse_keywords(&[ + Keyword::ALL, + Keyword::SOURCES, + Keyword::IN, + Keyword::SCHEMA, + ]) { + GrantObjects::AllSourcesInSchema { + schemas: self.parse_comma_separated(Parser::parse_object_name)?, + } + } else if self.parse_keywords(&[ + Keyword::ALL, + Keyword::MATERIALIZED, + Keyword::VIEWS, + Keyword::IN, + Keyword::SCHEMA, + ]) { + GrantObjects::AllMviewsInSchema { + schemas: self.parse_comma_separated(Parser::parse_object_name)?, + } + } else if self.parse_keywords(&[Keyword::MATERIALIZED, Keyword::VIEW]) { + GrantObjects::Mviews(self.parse_comma_separated(Parser::parse_object_name)?) } else { - let object_type = - self.parse_one_of_keywords(&[Keyword::SEQUENCE, Keyword::SCHEMA, Keyword::TABLE]); + let object_type = self.parse_one_of_keywords(&[ + Keyword::SEQUENCE, + Keyword::DATABASE, + Keyword::SCHEMA, + Keyword::TABLE, + Keyword::SOURCE, + ]); let objects = self.parse_comma_separated(Parser::parse_object_name); match object_type { + Some(Keyword::DATABASE) => GrantObjects::Databases(objects?), Some(Keyword::SCHEMA) => GrantObjects::Schemas(objects?), Some(Keyword::SEQUENCE) => GrantObjects::Sequences(objects?), + Some(Keyword::SOURCE) => GrantObjects::Sources(objects?), Some(Keyword::TABLE) | None => GrantObjects::Tables(objects?), _ => unreachable!(), } @@ -2906,6 +2936,8 @@ impl Parser { /// Parse a REVOKE statement pub fn parse_revoke(&mut self) -> Result { + let revoke_grant_option = + self.parse_keywords(&[Keyword::GRANT, Keyword::OPTION, Keyword::FOR]); let (privileges, objects) = self.parse_grant_revoke_privileges_objects()?; self.expect_keyword(Keyword::FROM)?; @@ -2926,6 +2958,7 @@ impl Parser { objects, grantees, granted_by, + revoke_grant_option, cascade, }) } diff --git a/src/sqlparser/tests/sqlparser_common.rs b/src/sqlparser/tests/sqlparser_common.rs index 050679a8929ba..6fc9ff5854e1b 100644 --- a/src/sqlparser/tests/sqlparser_common.rs +++ b/src/sqlparser/tests/sqlparser_common.rs @@ -3554,6 +3554,7 @@ fn test_revoke() { objects: GrantObjects::Tables(tables), grantees, cascade, + revoke_grant_option, granted_by, } => { assert_eq!( @@ -3571,6 +3572,7 @@ fn test_revoke() { grantees.iter().map(ToString::to_string).collect::>() ); assert!(cascade); + assert!(!revoke_grant_option); assert_eq!(None, granted_by); } _ => unreachable!(), diff --git a/src/sqlparser/tests/testdata/privilege.yaml b/src/sqlparser/tests/testdata/privilege.yaml new file mode 100644 index 0000000000000..d0403c185447b --- /dev/null +++ b/src/sqlparser/tests/testdata/privilege.yaml @@ -0,0 +1,39 @@ +- input: GRANT ALL ON DATABASE database TO user1 WITH GRANT OPTION GRANTED BY user + formatted_sql: GRANT ALL ON DATABASE database TO user1 WITH GRANT OPTION GRANTED BY user + formatted_ast: | + Grant { privileges: All { with_privileges_keyword: false }, objects: Databases([ObjectName([Ident { value: "database", quote_style: None }])]), grantees: [Ident { value: "user1", quote_style: None }], with_grant_option: true, granted_by: Some(Ident { value: "user", quote_style: None }) } + +- input: GRANT ALL ON SCHEMA database.schema1, database.schema2 TO user1 WITH GRANT OPTION GRANTED BY user + formatted_sql: GRANT ALL ON SCHEMA database.schema1, database.schema2 TO user1 WITH GRANT OPTION GRANTED BY user + formatted_ast: | + Grant { privileges: All { with_privileges_keyword: false }, objects: Schemas([ObjectName([Ident { value: "database", quote_style: None }, Ident { value: "schema1", quote_style: None }]), ObjectName([Ident { value: "database", quote_style: None }, Ident { value: "schema2", quote_style: None }])]), grantees: [Ident { value: "user1", quote_style: None }], with_grant_option: true, granted_by: Some(Ident { value: "user", quote_style: None }) } + +- input: GRANT ALL PRIVILEGES ON ALL SOURCES IN SCHEMA database.schema TO user1 GRANTED BY user + formatted_sql: GRANT ALL PRIVILEGES ON ALL SOURCES IN SCHEMA database.schema TO user1 GRANTED BY user + formatted_ast: | + Grant { privileges: All { with_privileges_keyword: true }, objects: AllSourcesInSchema { schemas: [ObjectName([Ident { value: "database", quote_style: None }, Ident { value: "schema", quote_style: None }])] }, grantees: [Ident { value: "user1", quote_style: None }], with_grant_option: false, granted_by: Some(Ident { value: "user", quote_style: None }) } + +- input: GRANT ALL PRIVILEGES ON ALL MATERIALIZED VIEWS IN SCHEMA database.schema TO user1 GRANTED BY user + formatted_sql: GRANT ALL PRIVILEGES ON ALL MATERIALIZED VIEWS IN SCHEMA database.schema TO user1 GRANTED BY user + formatted_ast: | + Grant { privileges: All { with_privileges_keyword: true }, objects: AllMviewsInSchema { schemas: [ObjectName([Ident { value: "database", quote_style: None }, Ident { value: "schema", quote_style: None }])] }, grantees: [Ident { value: "user1", quote_style: None }], with_grant_option: false, granted_by: Some(Ident { value: "user", quote_style: None }) } + +- input: REVOKE GRANT OPTION FOR ALL ON DATABASE database FROM user1 GRANTED BY user + formatted_sql: REVOKE GRANT OPTION FOR ALL ON DATABASE database FROM user1 GRANTED BY user RESTRICT + formatted_ast: | + Revoke { privileges: All { with_privileges_keyword: false }, objects: Databases([ObjectName([Ident { value: "database", quote_style: None }])]), grantees: [Ident { value: "user1", quote_style: None }], granted_by: Some(Ident { value: "user", quote_style: None }), revoke_grant_option: true, cascade: false } + +- input: REVOKE ALL PRIVILEGES ON DATABASE database FROM user1 GRANTED BY user + formatted_sql: REVOKE ALL PRIVILEGES ON DATABASE database FROM user1 GRANTED BY user RESTRICT + formatted_ast: | + Revoke { privileges: All { with_privileges_keyword: true }, objects: Databases([ObjectName([Ident { value: "database", quote_style: None }])]), grantees: [Ident { value: "user1", quote_style: None }], granted_by: Some(Ident { value: "user", quote_style: None }), revoke_grant_option: false, cascade: false } + +- input: REVOKE ALL PRIVILEGES ON ALL MATERIALIZED VIEWS IN SCHEMA database.schema FROM user1 + formatted_sql: REVOKE ALL PRIVILEGES ON ALL MATERIALIZED VIEWS IN SCHEMA database.schema FROM user1 RESTRICT + formatted_ast: | + Revoke { privileges: All { with_privileges_keyword: true }, objects: AllMviewsInSchema { schemas: [ObjectName([Ident { value: "database", quote_style: None }, Ident { value: "schema", quote_style: None }])] }, grantees: [Ident { value: "user1", quote_style: None }], granted_by: None, revoke_grant_option: false, cascade: false } + +- input: REVOKE ALL PRIVILEGES ON ALL SOURCES IN SCHEMA database.schema FROM user1 + formatted_sql: REVOKE ALL PRIVILEGES ON ALL SOURCES IN SCHEMA database.schema FROM user1 RESTRICT + formatted_ast: | + Revoke { privileges: All { with_privileges_keyword: true }, objects: AllSourcesInSchema { schemas: [ObjectName([Ident { value: "database", quote_style: None }, Ident { value: "schema", quote_style: None }])] }, grantees: [Ident { value: "user1", quote_style: None }], granted_by: None, revoke_grant_option: false, cascade: false } \ No newline at end of file diff --git a/src/utils/pgwire/src/pg_response.rs b/src/utils/pgwire/src/pg_response.rs index 70a766a324628..572b545d8aa04 100644 --- a/src/utils/pgwire/src/pg_response.rs +++ b/src/utils/pgwire/src/pg_response.rs @@ -36,6 +36,7 @@ pub enum StatementType { CREATE_SCHEMA, CREATE_USER, DESCRIBE_TABLE, + GRANT_PRIVILEGE, DROP_TABLE, DROP_MATERIALIZED_VIEW, DROP_INDEX, @@ -43,6 +44,7 @@ pub enum StatementType { DROP_SCHEMA, DROP_DATABASE, DROP_USER, + REVOKE_PRIVILEGE, // Introduce ORDER_BY statement type cuz Calcite unvalidated AST has SqlKind.ORDER_BY. Note // that Statement Type is not designed to be one to one mapping with SqlKind. ORDER_BY,