diff --git a/Cargo.lock b/Cargo.lock index ce71e2bf98950..d8977f920c990 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2735,6 +2735,26 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "enumflags2" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -7044,6 +7064,7 @@ dependencies = [ "educe", "either", "enum-as-inner", + "enumflags2", "ethnum", "fixedbitset", "fs-err", diff --git a/proto/user.proto b/proto/user.proto index 7468147cad271..c998f66d15133 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -43,6 +43,7 @@ message GrantPrivilege { DELETE = 4; CREATE = 5; CONNECT = 6; + USAGE = 7; } message ActionWithGrantOption { diff --git a/src/common/Cargo.toml b/src/common/Cargo.toml index ddd1fe5a33cdb..3f38ddc6700fa 100644 --- a/src/common/Cargo.toml +++ b/src/common/Cargo.toml @@ -37,6 +37,7 @@ crc32fast = "1" easy-ext = "1" educe = "0.4" either = "1" +enumflags2 = { version = "0.7.8" } enum-as-inner = "0.6" ethnum = { version = "1", features = ["serde"] } fixedbitset = { version = "0.4", features = ["std"] } diff --git a/src/common/src/acl/mod.rs b/src/common/src/acl/mod.rs new file mode 100644 index 0000000000000..929577437571d --- /dev/null +++ b/src/common/src/acl/mod.rs @@ -0,0 +1,144 @@ +// Copyright 2023 RisingWave Labs +// +// 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. + +//! `Acl` defines all grantable privileges. + +use std::convert::Into; +use std::fmt::Formatter; +use std::sync::LazyLock; + +use enumflags2::{bitflags, make_bitflags, BitFlags}; +use parse_display::Display; +use risingwave_pb::user::grant_privilege::PbAction; + +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] +pub enum AclMode { + #[display("a")] + Insert = 1 << 0, // formerly known as "append". + #[display("r")] + Select = 1 << 1, // formerly known as "read". + #[display("w")] + Update = 1 << 2, // formerly known as "write". + #[display("d")] + Delete = 1 << 3, + #[display("D")] + Truncate = 1 << 4, // super-delete, as it were + #[display("x")] + References = 1 << 5, + #[display("t")] + Trigger = 1 << 6, + #[display("X")] + Execute = 1 << 7, // For functions + #[display("U")] + Usage = 1 << 8, // For various object types + #[display("C")] + Create = 1 << 9, // For namespaces and databases + #[display("T")] + CreateTemp = 1 << 10, // For databases + #[display("c")] + Connect = 1 << 11, // For databases + #[display("s")] + Set = 1 << 12, // For configuration parameters + #[display("A")] + AlterSystem = 1 << 13, // For configuration parameters + #[display("m")] + Maintain = 1 << 14, // For relations +} + +impl From for AclMode { + fn from(action: PbAction) -> Self { + match action { + PbAction::Unspecified => unreachable!(), + PbAction::Select => AclMode::Select, + PbAction::Insert => AclMode::Insert, + PbAction::Update => AclMode::Update, + PbAction::Delete => AclMode::Delete, + PbAction::Create => AclMode::Create, + PbAction::Connect => AclMode::Connect, + PbAction::Usage => AclMode::Usage, + } + } +} + +impl From for PbAction { + fn from(val: AclMode) -> Self { + match val { + AclMode::Select => PbAction::Select, + AclMode::Insert => PbAction::Insert, + AclMode::Update => PbAction::Update, + AclMode::Delete => PbAction::Delete, + AclMode::Create => PbAction::Create, + AclMode::Connect => PbAction::Connect, + AclMode::Usage => PbAction::Usage, + _ => unreachable!(), + } + } +} + +/// `AclModeSet` defines a set of `AclMode`s. +#[derive(Clone, Debug)] +pub struct AclModeSet { + pub modes: BitFlags, +} + +pub static ALL_AVAILABLE_DATABASE_MODES: LazyLock = + LazyLock::new(|| make_bitflags!(AclMode::{Create | Connect}).into()); +pub static ALL_AVAILABLE_SCHEMA_MODES: LazyLock = + LazyLock::new(|| make_bitflags!(AclMode::{Create | Usage}).into()); +pub static ALL_AVAILABLE_TABLE_MODES: LazyLock = + LazyLock::new(|| make_bitflags!(AclMode::{Select | Insert | Update | Delete}).into()); +pub static ALL_AVAILABLE_SOURCE_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); +pub static ALL_AVAILABLE_MVIEW_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); +pub static ALL_AVAILABLE_VIEW_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); +pub static ALL_AVAILABLE_SINK_MODES: LazyLock = LazyLock::new(AclModeSet::empty); +pub static ALL_AVAILABLE_FUNCTION_MODES: LazyLock = + LazyLock::new(|| BitFlags::from(AclMode::Execute).into()); +pub static ALL_AVAILABLE_CONNECTION_MODES: LazyLock = + LazyLock::new(|| BitFlags::from(AclMode::Usage).into()); + +impl AclModeSet { + pub fn empty() -> Self { + Self { + modes: BitFlags::empty(), + } + } + + pub fn readonly() -> Self { + Self { + modes: BitFlags::from(AclMode::Select), + } + } + + pub fn has_mode(&self, mode: AclMode) -> bool { + self.modes.contains(mode) + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.modes.iter() + } +} + +impl From> for AclModeSet { + fn from(modes: BitFlags) -> Self { + Self { modes } + } +} + +impl std::fmt::Display for AclModeSet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.modes) + } +} diff --git a/src/common/src/lib.rs b/src/common/src/lib.rs index 2a3575d8dae78..589780a25668e 100644 --- a/src/common/src/lib.rs +++ b/src/common/src/lib.rs @@ -72,6 +72,7 @@ pub mod system_param; pub mod telemetry; pub mod transaction; +pub mod acl; pub mod metrics; pub mod test_utils; pub mod types; diff --git a/src/frontend/src/catalog/system_catalog/mod.rs b/src/frontend/src/catalog/system_catalog/mod.rs index 4cd271f0495b9..7b2420ed8d883 100644 --- a/src/frontend/src/catalog/system_catalog/mod.rs +++ b/src/frontend/src/catalog/system_catalog/mod.rs @@ -21,6 +21,7 @@ use std::sync::{Arc, LazyLock}; use async_trait::async_trait; use itertools::Itertools; +use risingwave_common::acl::AclMode; use risingwave_common::catalog::{ ColumnCatalog, ColumnDesc, Field, SysCatalogReader, TableDesc, TableId, DEFAULT_SUPER_USER_ID, NON_RESERVED_SYS_CATALOG_ID, @@ -28,8 +29,7 @@ use risingwave_common::catalog::{ use risingwave_common::error::Result; use risingwave_common::row::OwnedRow; use risingwave_common::types::DataType; -use risingwave_pb::user::grant_privilege::{Action, Object}; -use risingwave_pb::user::UserInfo; +use risingwave_pb::user::grant_privilege::Object; use crate::catalog::catalog_service::CatalogReader; use crate::catalog::system_catalog::information_schema::*; @@ -39,6 +39,7 @@ use crate::catalog::view_catalog::ViewCatalog; use crate::meta_client::FrontendMetaClient; use crate::scheduler::worker_node_manager::WorkerNodeManagerRef; use crate::session::AuthContext; +use crate::user::user_catalog::UserCatalog; use crate::user::user_privilege::available_prost_privilege; use crate::user::user_service::UserInfoReader; use crate::user::UserId; @@ -208,17 +209,17 @@ fn infer_dummy_view_sql(columns: &[SystemCatalogColumnsDef<'_>]) -> String { fn get_acl_items( object: &Object, for_dml_table: bool, - users: &Vec, + users: &Vec, username_map: &HashMap, ) -> String { let mut res = String::from("{"); let mut empty_flag = true; let super_privilege = available_prost_privilege(object.clone(), for_dml_table); for user in users { - let privileges = if user.get_is_super() { + let privileges = if user.is_super { vec![&super_privilege] } else { - user.get_grant_privileges() + user.grant_privileges .iter() .filter(|&privilege| privilege.object.as_ref().unwrap() == object) .collect_vec() @@ -229,43 +230,29 @@ fn get_acl_items( let mut grantor_map = HashMap::new(); privileges.iter().for_each(|&privilege| { privilege.action_with_opts.iter().for_each(|ao| { - grantor_map.entry(ao.granted_by).or_insert_with(Vec::new); grantor_map - .get_mut(&ao.granted_by) - .unwrap() - .push((ao.action, ao.with_grant_option)); + .entry(ao.granted_by) + .or_insert_with(Vec::new) + .push((ao.get_action().unwrap(), ao.with_grant_option)); }) }); - for key in grantor_map.keys() { + for (granted_by, actions) in grantor_map { if empty_flag { empty_flag = false; } else { res.push(','); } - res.push_str(user.get_name()); + res.push_str(&user.name); res.push('='); - grantor_map - .get(key) - .unwrap() - .iter() - .for_each(|(action, option)| { - let str = match Action::try_from(*action).unwrap() { - Action::Select => "r", - Action::Insert => "a", - Action::Update => "w", - Action::Delete => "d", - Action::Create => "C", - Action::Connect => "c", - _ => unreachable!(), - }; - res.push_str(str); - if *option { - res.push('*'); - } - }); + for (action, option) in actions { + res.push_str(&AclMode::from(action).to_string()); + if option { + res.push('*'); + } + } res.push('/'); // should be able to query grantor's name - res.push_str(username_map.get(key).as_ref().unwrap()); + res.push_str(username_map.get(&granted_by).unwrap()); } } res.push('}'); diff --git a/src/frontend/src/handler/alter_user.rs b/src/frontend/src/handler/alter_user.rs index 5c91df3888b71..0d83c3ae867d5 100644 --- a/src/frontend/src/handler/alter_user.rs +++ b/src/frontend/src/handler/alter_user.rs @@ -24,11 +24,12 @@ use crate::binder::Binder; use crate::catalog::CatalogError; use crate::handler::HandlerArgs; use crate::user::user_authentication::encrypted_password; +use crate::user::user_catalog::UserCatalog; fn alter_prost_user_info( mut user_info: UserInfo, options: &UserOptions, - session_user: &UserInfo, + session_user: &UserCatalog, ) -> Result<(UserInfo, Vec)> { if !session_user.is_super { let require_super = user_info.is_super @@ -116,7 +117,7 @@ fn alter_prost_user_info( fn alter_rename_prost_user_info( mut user_info: UserInfo, new_name: ObjectName, - session_user: &UserInfo, + session_user: &UserCatalog, ) -> Result<(UserInfo, Vec)> { if session_user.id == user_info.id { return Err(InternalError("session user cannot be renamed".to_string()).into()); @@ -153,7 +154,7 @@ pub async fn handle_alter_user( let old_info = user_reader .get_user_by_name(&user_name) .ok_or(CatalogError::NotFound("user", user_name))? - .clone(); + .to_prost(); let session_user = user_reader .get_user_by_name(session.user_name()) diff --git a/src/frontend/src/handler/create_index.rs b/src/frontend/src/handler/create_index.rs index a5a002d3b3d79..bed35eadec9ae 100644 --- a/src/frontend/src/handler/create_index.rs +++ b/src/frontend/src/handler/create_index.rs @@ -18,12 +18,13 @@ use std::rc::Rc; use fixedbitset::FixedBitSet; use itertools::Itertools; use pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::acl::AclMode; use risingwave_common::catalog::{IndexId, TableDesc, TableId}; use risingwave_common::error::{ErrorCode, Result}; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_pb::catalog::{PbIndex, PbStreamJobStatus, PbTable}; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; -use risingwave_pb::user::grant_privilege::{Action, Object}; +use risingwave_pb::user::grant_privilege::Object; use risingwave_sqlparser::ast; use risingwave_sqlparser::ast::{Ident, ObjectName, OrderByExpr}; @@ -74,7 +75,7 @@ pub(crate) fn gen_create_index_plan( session.check_privileges(&[ObjectCheckItem::new( table.owner, - Action::Select, + AclMode::Select, Object::TableId(table.id.table_id), )])?; diff --git a/src/frontend/src/handler/create_mv.rs b/src/frontend/src/handler/create_mv.rs index 053ba5aa30f19..75474ca576dd8 100644 --- a/src/frontend/src/handler/create_mv.rs +++ b/src/frontend/src/handler/create_mv.rs @@ -14,10 +14,10 @@ use itertools::Itertools; use pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::acl::AclMode; use risingwave_common::error::{ErrorCode, Result}; use risingwave_pb::catalog::{CreateType, PbTable}; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; -use risingwave_pb::user::grant_privilege::Action; use risingwave_sqlparser::ast::{EmitMode, Ident, ObjectName, Query}; use super::privilege::resolve_relation_privileges; @@ -66,7 +66,7 @@ pub(super) fn get_column_names( } if let Some(relation) = &select.from { let mut check_items = Vec::new(); - resolve_relation_privileges(relation, Action::Select, &mut check_items); + resolve_relation_privileges(relation, AclMode::Select, &mut check_items); session.check_privileges(&check_items)?; } } diff --git a/src/frontend/src/handler/create_schema.rs b/src/frontend/src/handler/create_schema.rs index 12970193fe33e..962d59178bf0c 100644 --- a/src/frontend/src/handler/create_schema.rs +++ b/src/frontend/src/handler/create_schema.rs @@ -13,9 +13,10 @@ // limitations under the License. use pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::acl::AclMode; use risingwave_common::catalog::RESERVED_PG_SCHEMA_PREFIX; use risingwave_common::error::{ErrorCode, Result}; -use risingwave_pb::user::grant_privilege::{Action, Object}; +use risingwave_pb::user::grant_privilege::Object; use risingwave_sqlparser::ast::ObjectName; use super::RwPgResponse; @@ -63,7 +64,7 @@ pub async fn handle_create_schema( session.check_privileges(&[ObjectCheckItem::new( db_owner, - Action::Create, + AclMode::Create, Object::DatabaseId(db_id), )])?; diff --git a/src/frontend/src/handler/create_user.rs b/src/frontend/src/handler/create_user.rs index a4f8eaecc405e..8659e1b647c33 100644 --- a/src/frontend/src/handler/create_user.rs +++ b/src/frontend/src/handler/create_user.rs @@ -24,11 +24,12 @@ use crate::binder::Binder; use crate::catalog::{CatalogError, DatabaseId}; use crate::handler::HandlerArgs; use crate::user::user_authentication::encrypted_password; +use crate::user::user_catalog::UserCatalog; fn make_prost_user_info( user_name: String, options: &UserOptions, - session_user: &UserInfo, + session_user: &UserCatalog, database_id: DatabaseId, ) -> Result { if !session_user.is_super { diff --git a/src/frontend/src/handler/drop_user.rs b/src/frontend/src/handler/drop_user.rs index 11232faa64a95..5b0ae7d55596c 100644 --- a/src/frontend/src/handler/drop_user.rs +++ b/src/frontend/src/handler/drop_user.rs @@ -34,14 +34,14 @@ pub async fn handle_drop_user( let user_name = Binder::resolve_user_name(user_name)?; let user_info_reader = session.env().user_info_reader(); - let user = user_info_reader + let user_id = user_info_reader .read_guard() .get_user_by_name(&user_name) - .cloned(); - match user { - Some(user) => { + .map(|u| u.id); + match user_id { + Some(user_id) => { let user_info_writer = session.user_info_writer()?; - user_info_writer.drop_user(user.id).await?; + user_info_writer.drop_user(user_id).await?; } None => { return if if_exists { diff --git a/src/frontend/src/handler/handle_privilege.rs b/src/frontend/src/handler/handle_privilege.rs index 2f166e9fa1824..07b87fa3bc3d2 100644 --- a/src/frontend/src/handler/handle_privilege.rs +++ b/src/frontend/src/handler/handle_privilege.rs @@ -39,7 +39,10 @@ fn make_prost_privilege( let reader = catalog_reader.read_guard(); let actions = match privileges { Privileges::All { .. } => available_privilege_actions(&objects)?, - Privileges::Actions(actions) => actions, + Privileges::Actions(actions) => actions + .into_iter() + .map(|action| get_prost_action(&action)) + .collect(), }; let mut grant_objs = vec![]; match objects { @@ -147,14 +150,11 @@ fn make_prost_privilege( } }; let action_with_opts = actions - .iter() - .map(|action| { - let prost_action = get_prost_action(action); - ActionWithGrantOption { - action: prost_action as i32, - granted_by: session.user_id(), - ..Default::default() - } + .into_iter() + .map(|action| ActionWithGrantOption { + action: action as i32, + granted_by: session.user_id(), + ..Default::default() }) .collect::>(); @@ -318,12 +318,12 @@ mod tests { PbGrantPrivilege { action_with_opts: vec![ ActionWithGrantOption { - action: Action::Connect as i32, + action: Action::Create as i32, with_grant_option: true, granted_by: DEFAULT_SUPER_USER_ID, }, ActionWithGrantOption { - action: Action::Create as i32, + action: Action::Connect as i32, with_grant_option: true, granted_by: DEFAULT_SUPER_USER_ID, } diff --git a/src/frontend/src/handler/privilege.rs b/src/frontend/src/handler/privilege.rs index dcd8696b9670f..24227a46ff2a6 100644 --- a/src/frontend/src/handler/privilege.rs +++ b/src/frontend/src/handler/privilege.rs @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use risingwave_common::acl::AclMode; use risingwave_common::error::ErrorCode::PermissionDenied; use risingwave_common::error::Result; -use risingwave_pb::user::grant_privilege::{PbAction, PbObject}; +use risingwave_pb::user::grant_privilege::PbObject; use crate::binder::{BoundQuery, BoundStatement, Relation}; use crate::catalog::OwnedByUserCatalog; @@ -24,15 +25,16 @@ use crate::user::UserId; #[derive(Debug)] pub struct ObjectCheckItem { owner: UserId, - action: PbAction, + mode: AclMode, + // todo: change it to object id. object: PbObject, } impl ObjectCheckItem { - pub fn new(owner: UserId, action: PbAction, object: PbObject) -> Self { + pub fn new(owner: UserId, mode: AclMode, object: PbObject) -> Self { Self { owner, - action, + mode, object, } } @@ -41,14 +43,14 @@ impl ObjectCheckItem { /// resolve privileges in `relation` pub(crate) fn resolve_relation_privileges( relation: &Relation, - action: PbAction, + mode: AclMode, objects: &mut Vec, ) { match relation { Relation::Source(source) => { let item = ObjectCheckItem { owner: source.catalog.owner, - action, + mode, object: PbObject::SourceId(source.catalog.id), }; objects.push(item); @@ -56,7 +58,7 @@ pub(crate) fn resolve_relation_privileges( Relation::BaseTable(table) => { let item = ObjectCheckItem { owner: table.table_catalog.owner, - action, + mode, object: PbObject::TableId(table.table_id.table_id), }; objects.push(item); @@ -64,16 +66,16 @@ pub(crate) fn resolve_relation_privileges( Relation::Subquery(query) => { if let crate::binder::BoundSetExpr::Select(select) = &query.query.body { if let Some(sub_relation) = &select.from { - resolve_relation_privileges(sub_relation, action, objects); + resolve_relation_privileges(sub_relation, mode, objects); } } } Relation::Join(join) => { - resolve_relation_privileges(&join.left, action, objects); - resolve_relation_privileges(&join.right, action, objects); + resolve_relation_privileges(&join.left, mode, objects); + resolve_relation_privileges(&join.right, mode, objects); } Relation::WindowTableFunction(table) => { - resolve_relation_privileges(&table.input, action, objects) + resolve_relation_privileges(&table.input, mode, objects) } _ => {} }; @@ -86,20 +88,20 @@ pub(crate) fn resolve_privileges(stmt: &BoundStatement) -> Vec BoundStatement::Insert(ref insert) => { let object = ObjectCheckItem { owner: insert.owner, - action: PbAction::Insert, + mode: AclMode::Insert, object: PbObject::TableId(insert.table_id.table_id), }; objects.push(object); if let crate::binder::BoundSetExpr::Select(select) = &insert.source.body { if let Some(sub_relation) = &select.from { - resolve_relation_privileges(sub_relation, PbAction::Select, &mut objects); + resolve_relation_privileges(sub_relation, AclMode::Select, &mut objects); } } } BoundStatement::Delete(ref delete) => { let object = ObjectCheckItem { owner: delete.owner, - action: PbAction::Delete, + mode: AclMode::Delete, object: PbObject::TableId(delete.table_id.table_id), }; objects.push(object); @@ -107,7 +109,7 @@ pub(crate) fn resolve_privileges(stmt: &BoundStatement) -> Vec BoundStatement::Update(ref update) => { let object = ObjectCheckItem { owner: update.owner, - action: PbAction::Update, + mode: AclMode::Update, object: PbObject::TableId(update.table_id.table_id), }; objects.push(object); @@ -122,7 +124,7 @@ pub(crate) fn resolve_query_privileges(query: &BoundQuery) -> Vec Result<()> { let mut lock = self.user_info.write(); let id = update_user.get_id(); - let old_name = lock.get_user_name_by_id(id).unwrap(); - let mut user_info = lock.get_user_by_name(&old_name).unwrap().clone(); + let Some(old_name) = lock.get_user_name_by_id(id) else { + return Ok(()); + }; + let mut user_info = lock.get_user_by_name(&old_name).unwrap().to_prost(); update_fields.into_iter().for_each(|field| match field { UpdateField::Super => user_info.is_super = update_user.is_super, UpdateField::Login => user_info.can_login = update_user.can_login, @@ -675,7 +677,7 @@ impl UserInfoWriter for MockUserInfoWriter { .collect::>(); for user_id in users { if let Some(u) = self.user_info.write().get_user_mut(user_id) { - u.grant_privileges.extend(privileges.clone()); + u.extend_privileges(privileges.clone()); } } Ok(()) @@ -694,32 +696,7 @@ impl UserInfoWriter for MockUserInfoWriter { ) -> Result<()> { for user_id in users { if let Some(u) = self.user_info.write().get_user_mut(user_id) { - 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) - }); - } - } - }); - u.grant_privileges - .retain(|p| !p.action_with_opts.is_empty()); + u.revoke_privileges(privileges.clone(), revoke_grant_option); } } Ok(()) diff --git a/src/frontend/src/user/mod.rs b/src/frontend/src/user/mod.rs index c0e23f2a69d92..6dfec976ac7f8 100644 --- a/src/frontend/src/user/mod.rs +++ b/src/frontend/src/user/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. pub(crate) mod user_authentication; +pub(crate) mod user_catalog; pub(crate) mod user_manager; pub mod user_privilege; pub(crate) mod user_service; diff --git a/src/frontend/src/user/user_catalog.rs b/src/frontend/src/user/user_catalog.rs new file mode 100644 index 0000000000000..e1b5f85d446b1 --- /dev/null +++ b/src/frontend/src/user/user_catalog.rs @@ -0,0 +1,171 @@ +// Copyright 2023 RisingWave Labs +// +// 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 std::collections::hash_map::Entry; +use std::collections::HashMap; + +use risingwave_common::acl::{AclMode, AclModeSet}; +use risingwave_pb::user::grant_privilege::{Object as GrantObject, Object}; +use risingwave_pb::user::{PbAuthInfo, PbGrantPrivilege, PbUserInfo}; + +use crate::catalog::{DatabaseId, SchemaId}; +use crate::user::UserId; + +/// `UserCatalog` is responsible for managing user's information. +#[derive(Clone, Debug)] +pub struct UserCatalog { + pub id: UserId, + pub name: String, + pub is_super: bool, + pub can_create_db: bool, + pub can_create_user: bool, + pub can_login: bool, + pub auth_info: Option, + pub grant_privileges: Vec, + + // User owned acl mode set, group by object id. + // TODO: merge it after we fully migrate to sql-backend. + pub database_acls: HashMap, + pub schema_acls: HashMap, + pub object_acls: HashMap, +} + +impl From for UserCatalog { + fn from(user: PbUserInfo) -> Self { + let mut user_catalog = Self { + id: user.id, + name: user.name, + is_super: user.is_super, + can_create_db: user.can_create_db, + can_create_user: user.can_create_user, + can_login: user.can_login, + auth_info: user.auth_info, + grant_privileges: user.grant_privileges, + database_acls: Default::default(), + schema_acls: Default::default(), + object_acls: Default::default(), + }; + user_catalog.refresh_acl_modes(); + + user_catalog + } +} + +impl UserCatalog { + pub fn to_prost(&self) -> PbUserInfo { + PbUserInfo { + id: self.id, + name: self.name.clone(), + is_super: self.is_super, + can_create_db: self.can_create_db, + can_create_user: self.can_create_user, + can_login: self.can_login, + auth_info: self.auth_info.clone(), + grant_privileges: self.grant_privileges.clone(), + } + } + + fn get_acl_entry(&mut self, object: GrantObject) -> Entry<'_, u32, AclModeSet> { + match object { + Object::DatabaseId(id) => self.database_acls.entry(id), + Object::SchemaId(id) => self.schema_acls.entry(id), + Object::TableId(id) => self.object_acls.entry(id), + Object::SourceId(id) => self.object_acls.entry(id), + Object::SinkId(id) => self.object_acls.entry(id), + Object::ViewId(id) => self.object_acls.entry(id), + Object::FunctionId(_) => { + unreachable!("grant privilege on function is not supported yet.") + } + _ => unreachable!(""), + } + } + + fn get_acl(&self, object: &GrantObject) -> Option<&AclModeSet> { + match object { + Object::DatabaseId(id) => self.database_acls.get(id), + Object::SchemaId(id) => self.schema_acls.get(id), + Object::TableId(id) => self.object_acls.get(id), + Object::SourceId(id) => self.object_acls.get(id), + Object::SinkId(id) => self.object_acls.get(id), + Object::ViewId(id) => self.object_acls.get(id), + Object::FunctionId(_) => { + unreachable!("grant privilege on function is not supported yet.") + } + _ => unreachable!("unexpected object type."), + } + } + + fn refresh_acl_modes(&mut self) { + self.database_acls.clear(); + self.schema_acls.clear(); + self.object_acls.clear(); + let privileges = self.grant_privileges.clone(); + for privilege in privileges { + let entry = self + .get_acl_entry(privilege.object.unwrap()) + .or_insert(AclModeSet::empty()); + for awo in privilege.action_with_opts { + entry + .modes + .insert::(awo.get_action().unwrap().into()); + } + } + } + + // Only for test, used in `MockUserInfoWriter`. + pub fn extend_privileges(&mut self, privileges: Vec) { + self.grant_privileges.extend(privileges); + self.refresh_acl_modes(); + } + + // Only for test, used in `MockUserInfoWriter`. + pub fn revoke_privileges( + &mut self, + privileges: Vec, + revoke_grant_option: bool, + ) { + self.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) + }); + } + } + }); + self.grant_privileges + .retain(|p| !p.action_with_opts.is_empty()); + self.refresh_acl_modes(); + } + + pub fn check_privilege(&self, object: &GrantObject, mode: AclMode) -> bool { + self.get_acl(object) + .map_or(false, |acl_set| acl_set.has_mode(mode)) + } +} diff --git a/src/frontend/src/user/user_manager.rs b/src/frontend/src/user/user_manager.rs index 3620eef51114a..d42c764c0b5ed 100644 --- a/src/frontend/src/user/user_manager.rs +++ b/src/frontend/src/user/user_manager.rs @@ -17,12 +17,13 @@ use std::collections::HashMap; use itertools::Itertools; use risingwave_pb::user::{GrantPrivilege, UserInfo}; +use crate::user::user_catalog::UserCatalog; use crate::user::{UserId, UserInfoVersion}; /// `UserInfoManager` is responsible for managing users. pub struct UserInfoManager { version: UserInfoVersion, - user_by_name: HashMap, + user_by_name: HashMap, user_name_by_id: HashMap, } @@ -38,16 +39,16 @@ impl Default for UserInfoManager { } impl UserInfoManager { - pub fn get_user_mut(&mut self, id: UserId) -> Option<&mut UserInfo> { + pub fn get_user_mut(&mut self, id: UserId) -> Option<&mut UserCatalog> { let name = self.user_name_by_id.get(&id)?; self.user_by_name.get_mut(name) } - pub fn get_all_users(&self) -> Vec { + pub fn get_all_users(&self) -> Vec { self.user_by_name.values().cloned().collect_vec() } - pub fn get_user_by_name(&self, user_name: &str) -> Option<&UserInfo> { + pub fn get_user_by_name(&self, user_name: &str) -> Option<&UserCatalog> { self.user_by_name.get(user_name) } @@ -63,7 +64,7 @@ impl UserInfoManager { let id = user_info.id; let name = user_info.name.clone(); self.user_by_name - .try_insert(name.clone(), user_info) + .try_insert(name.clone(), user_info.into()) .unwrap(); self.user_name_by_id.try_insert(id, name).unwrap(); } @@ -78,9 +79,11 @@ impl UserInfoManager { let name = user_info.name.clone(); if let Some(old_name) = self.get_user_name_by_id(id) { self.user_by_name.remove(&old_name); - self.user_by_name.insert(name.clone(), user_info); + self.user_by_name.insert(name.clone(), user_info.into()); } else { - self.user_by_name.insert(name.clone(), user_info).unwrap(); + self.user_by_name + .insert(name.clone(), user_info.into()) + .unwrap(); } self.user_name_by_id.insert(id, name).unwrap(); } diff --git a/src/frontend/src/user/user_privilege.rs b/src/frontend/src/user/user_privilege.rs index 69ea6ce6830f0..983b38e91d562 100644 --- a/src/frontend/src/user/user_privilege.rs +++ b/src/frontend/src/user/user_privilege.rs @@ -12,55 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::LazyLock; + use itertools::Itertools; +use risingwave_common::acl; +use risingwave_common::acl::{AclMode, AclModeSet}; use risingwave_common::catalog::DEFAULT_SUPER_USER_ID; use risingwave_common::error::{ErrorCode, Result}; use risingwave_pb::user::grant_privilege::{ActionWithGrantOption, PbAction, PbObject}; use risingwave_pb::user::PbGrantPrivilege; use risingwave_sqlparser::ast::{Action, GrantObjects, Privileges}; -// 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_TABLE: &[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 }]; -static AVAILABLE_ACTION_ON_VIEW: &[Action] = AVAILABLE_ACTION_ON_MVIEW; -static AVAILABLE_ACTION_ON_SOURCE: &[Action] = AVAILABLE_ACTION_ON_MVIEW; -static AVAILABLE_ACTION_ON_SINK: &[Action] = &[]; -static AVAILABLE_ACTION_ON_FUNCTION: &[Action] = &[]; - pub 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)), - GrantObjects::Tables(_) | GrantObjects::AllTablesInSchema { .. } => actions - .iter() - .all(|action| AVAILABLE_ACTION_ON_TABLE.contains(action)), - GrantObjects::Sinks(_) => actions - .iter() - .all(|action| AVAILABLE_ACTION_ON_SINK.contains(action)), - GrantObjects::Sequences(_) | GrantObjects::AllSequencesInSchema { .. } => true, - }; + let acl_sets = get_all_available_modes(objects)?; + let valid = actions + .iter() + .map(get_prost_action) + .all(|action| acl_sets.has_mode(action.into())); if !valid { return Err(ErrorCode::BindError( "Invalid privilege type for the given object.".to_string(), @@ -73,25 +44,33 @@ pub fn check_privilege_type(privilege: &Privileges, objects: &GrantObjects) -> R } } -pub 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()), +fn get_all_available_modes( + object: &GrantObjects, +) -> Result<&LazyLock AclModeSet>> { + match object { + GrantObjects::Databases(_) => Ok(&acl::ALL_AVAILABLE_DATABASE_MODES), + GrantObjects::Schemas(_) => Ok(&acl::ALL_AVAILABLE_SCHEMA_MODES), GrantObjects::Sources(_) | GrantObjects::AllSourcesInSchema { .. } => { - Ok(AVAILABLE_ACTION_ON_SOURCE.to_vec()) + Ok(&acl::ALL_AVAILABLE_SOURCE_MODES) } GrantObjects::Mviews(_) | GrantObjects::AllMviewsInSchema { .. } => { - Ok(AVAILABLE_ACTION_ON_MVIEW.to_vec()) + Ok(&acl::ALL_AVAILABLE_MVIEW_MODES) } GrantObjects::Tables(_) | GrantObjects::AllTablesInSchema { .. } => { - Ok(AVAILABLE_ACTION_ON_TABLE.to_vec()) + Ok(&acl::ALL_AVAILABLE_TABLE_MODES) } + GrantObjects::Sinks(_) => Ok(&acl::ALL_AVAILABLE_SINK_MODES), _ => Err( ErrorCode::BindError("Invalid privilege type for the given object.".to_string()).into(), ), } } +pub fn available_privilege_actions(objects: &GrantObjects) -> Result> { + let acl_sets = get_all_available_modes(objects)?; + Ok(acl_sets.iter().map(Into::into).collect_vec()) +} + #[inline(always)] pub fn get_prost_action(action: &Action) -> PbAction { match action { @@ -101,31 +80,32 @@ pub fn get_prost_action(action: &Action) -> PbAction { Action::Delete { .. } => PbAction::Delete, Action::Connect => PbAction::Connect, Action::Create => PbAction::Create, + Action::Usage => PbAction::Usage, _ => unreachable!(), } } pub fn available_prost_privilege(object: PbObject, for_dml_table: bool) -> PbGrantPrivilege { - let actions = match object { - PbObject::DatabaseId(_) => AVAILABLE_ACTION_ON_DATABASE.to_vec(), - PbObject::SchemaId(_) => AVAILABLE_ACTION_ON_SCHEMA.to_vec(), - PbObject::SourceId(_) => AVAILABLE_ACTION_ON_SOURCE.to_vec(), + let acl_set = match object { + PbObject::DatabaseId(_) => &acl::ALL_AVAILABLE_DATABASE_MODES, + PbObject::SchemaId(_) => &acl::ALL_AVAILABLE_SCHEMA_MODES, + PbObject::SourceId(_) => &acl::ALL_AVAILABLE_SOURCE_MODES, PbObject::TableId(_) => { if for_dml_table { - AVAILABLE_ACTION_ON_TABLE.to_vec() + &acl::ALL_AVAILABLE_TABLE_MODES } else { - AVAILABLE_ACTION_ON_MVIEW.to_vec() + &acl::ALL_AVAILABLE_MVIEW_MODES } } - PbObject::ViewId(_) => AVAILABLE_ACTION_ON_VIEW.to_vec(), - PbObject::SinkId(_) => AVAILABLE_ACTION_ON_SINK.to_vec(), - PbObject::FunctionId(_) => AVAILABLE_ACTION_ON_FUNCTION.to_vec(), + PbObject::ViewId(_) => &acl::ALL_AVAILABLE_VIEW_MODES, + PbObject::SinkId(_) => &acl::ALL_AVAILABLE_SINK_MODES, + PbObject::FunctionId(_) => &acl::ALL_AVAILABLE_FUNCTION_MODES, _ => unreachable!("Invalid object type"), }; - let actions = actions + let actions = acl_set .iter() - .map(|action| ActionWithGrantOption { - action: get_prost_action(action) as i32, + .map(|mode| ActionWithGrantOption { + action: >::into(mode) as i32, with_grant_option: false, granted_by: DEFAULT_SUPER_USER_ID, })