Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rbac): add GrantObject::Warehouse #17029

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/exception/src/exception_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ build_exceptions! {
// Cluster error codes.
ClusterUnknownNode(2401),
ClusterNodeAlreadyExists(2402),
InvalidWarehouse(2403),

// Stage error codes.
UnknownStage(2501),
Expand Down
11 changes: 9 additions & 2 deletions src/meta/app/src/principal/user_grant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub enum GrantObject {
TableById(String, u64, u64),
UDF(String),
Stage(String),
Warehouse(String),
}

impl GrantObject {
Expand Down Expand Up @@ -89,6 +90,7 @@ impl GrantObject {
(GrantObject::Table(_, _, _), _) => false,
(GrantObject::Stage(lstage), GrantObject::Stage(rstage)) => lstage == rstage,
(GrantObject::UDF(udf), GrantObject::UDF(rudf)) => udf == rudf,
(GrantObject::Warehouse(w), GrantObject::Warehouse(rw)) => w == rw,
_ => false,
}
}
Expand All @@ -109,12 +111,16 @@ impl GrantObject {
GrantObject::Stage(_) => {
UserPrivilegeSet::available_privileges_on_stage(available_ownership)
}
GrantObject::Warehouse(_) => UserPrivilegeSet::available_privileges_on_warehouse(),
}
}

pub fn catalog(&self) -> Option<String> {
match self {
GrantObject::Global | GrantObject::Stage(_) | GrantObject::UDF(_) => None,
GrantObject::Global
| GrantObject::Stage(_)
| GrantObject::UDF(_)
| GrantObject::Warehouse(_) => None,
GrantObject::Database(cat, _) | GrantObject::DatabaseById(cat, _) => Some(cat.clone()),
GrantObject::Table(cat, _, _) | GrantObject::TableById(cat, _, _) => Some(cat.clone()),
}
Expand All @@ -135,6 +141,7 @@ impl fmt::Display for GrantObject {
}
GrantObject::UDF(udf) => write!(f, "UDF {udf}"),
GrantObject::Stage(stage) => write!(f, "STAGE {stage}"),
GrantObject::Warehouse(w) => write!(f, "WAREHOUSE {w}"),
}
}
}
Expand Down Expand Up @@ -179,7 +186,7 @@ impl GrantEntry {
}

impl fmt::Display for GrantEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let privileges: UserPrivilegeSet = self.privileges.into();
let privileges_str = if self.has_all_available_privileges() {
"ALL".to_string()
Expand Down
8 changes: 7 additions & 1 deletion src/meta/app/src/principal/user_privilege.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use enumflags2::BitFlags;
num_derive::FromPrimitive,
)]
pub enum UserPrivilegeType {
// UsagePrivilege is a synonym for “no privileges”, if object is udf, means can use this udf
// UsagePrivilege is a synonym for “no privileges”, if object is udf/warehouse, means can use this udf/warehouse
Usage = 1 << 0,
// Privilege to select rows from tables in a database.
Select = 1 << 2,
Expand Down Expand Up @@ -199,10 +199,12 @@ impl UserPrivilegeSet {
let database_privs = Self::available_privileges_on_database(false);
let stage_privs_without_ownership = Self::available_privileges_on_stage(false);
let udf_privs_without_ownership = Self::available_privileges_on_udf(false);
let wh_privs_without_ownership = Self::available_privileges_on_warehouse();
let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask });
(database_privs.privileges
| privs
| stage_privs_without_ownership.privileges
| wh_privs_without_ownership.privileges
| udf_privs_without_ownership.privileges)
.into()
}
Expand Down Expand Up @@ -232,6 +234,10 @@ impl UserPrivilegeSet {
}
}

pub fn available_privileges_on_warehouse() -> Self {
make_bitflags!(UserPrivilegeType::{ Usage }).into()
}

pub fn available_privileges_on_udf(available_ownership: bool) -> Self {
if available_ownership {
make_bitflags!(UserPrivilegeType::{ Usage | Ownership }).into()
Expand Down
8 changes: 8 additions & 0 deletions src/meta/proto-conv/src/user_from_to_protobuf_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ impl FromToProto for mt::principal::GrantObject {
pb::grant_object::Object::Stage(pb::grant_object::GrantStageObject { stage }) => {
Ok(mt::principal::GrantObject::Stage(stage))
}
pb::grant_object::Object::Warehouse(pb::grant_object::GrantWarehouseObject {
warehouse,
}) => Ok(mt::principal::GrantObject::Warehouse(warehouse)),
}
}

Expand Down Expand Up @@ -235,6 +238,11 @@ impl FromToProto for mt::principal::GrantObject {
stage: stage.clone(),
},
)),
mt::principal::GrantObject::Warehouse(w) => Some(pb::grant_object::Object::Warehouse(
pb::grant_object::GrantWarehouseObject {
warehouse: w.clone(),
},
)),
};
Ok(pb::GrantObject {
ver: VER,
Expand Down
1 change: 1 addition & 0 deletions src/meta/proto-conv/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[
(110, "2024-09-18: Add: database.proto: DatabaseMeta.gc_in_progress"),
(111, "2024-11-13: Add: Enable AWS Glue as an Apache Iceberg type when creating a catalog."),
(112, "2024-11-28: Add: virtual_column add data_types field"),
(113, "2024-12-10: Add: GrantWarehouseObject"),
// Dear developer:
// If you're gonna add a new metadata version, you'll have to add a test for it.
// You could just copy an existing test file(e.g., `../tests/it/v024_table_meta.rs`)
Expand Down
1 change: 1 addition & 0 deletions src/meta/proto-conv/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ mod v109_procedure_with_args;
mod v110_database_meta_gc_in_progress;
mod v111_add_glue_as_iceberg_catalog_option;
mod v112_virtual_column;
mod v113_warehouse_grantobject;
40 changes: 40 additions & 0 deletions src/meta/proto-conv/tests/it/v113_warehouse_grantobject.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2023 Datafuse 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 databend_common_meta_app as mt;
use fastrace::func_name;

use crate::common;

// These bytes are built when a new version in introduced,
// and are kept for backward compatibility test.
//
// *************************************************************
// * These messages should never be updated, *
// * only be added when a new version is added, *
// * or be removed when an old version is no longer supported. *
// *************************************************************
//

#[test]
fn test_decode_v113_grant_object() -> anyhow::Result<()> {
let grant_object_v113 = vec![66, 4, 10, 2, 119, 116, 160, 6, 113, 168, 6, 24];

let want = || mt::principal::GrantObject::Warehouse("wt".to_string());

common::test_pb_from_to(func_name!(), want())?;
common::test_load_old(func_name!(), grant_object_v113.as_slice(), 113, want())?;

Ok(())
}
5 changes: 5 additions & 0 deletions src/meta/protos/proto/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ message GrantObject {
string stage = 1;
}

message GrantWarehouseObject {
string warehouse = 1;
}

oneof object {
GrantGlobalObject global = 1;
GrantDatabaseObject database = 2;
Expand All @@ -85,6 +89,7 @@ message GrantObject {
GrantStageObject stage = 5;
GrantDatabaseIdObject databasebyid = 6;
GrantTableIdObject tablebyid = 7;
GrantWarehouseObject warehouse = 8;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/query/ast/src/ast/statements/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ pub enum AccountMgrLevel {
Table(Option<String>, String),
UDF(String),
Stage(String),
Warehouse(String),
}

impl Display for AccountMgrLevel {
Expand All @@ -242,6 +243,7 @@ impl Display for AccountMgrLevel {
}
AccountMgrLevel::UDF(udf) => write!(f, " UDF {udf}"),
AccountMgrLevel::Stage(stage) => write!(f, " STAGE {stage}"),
AccountMgrLevel::Warehouse(w) => write!(f, " WAREHOUSE {w}"),
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/query/ast/src/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3084,9 +3084,31 @@ pub fn grant_source(i: Input) -> IResult<AccountMgrSource> {
},
);

let warehouse_privs = map(
rule! {
USAGE ~ ON ~ WAREHOUSE ~ #ident
},
|(_, _, _, w)| AccountMgrSource::Privs {
privileges: vec![UserPrivilegeType::Usage],
level: AccountMgrLevel::Warehouse(w.to_string()),
},
);

let warehouse_all_privs = map(
rule! {
ALL ~ PRIVILEGES? ~ ON ~ WAREHOUSE ~ #ident
},
|(_, _, _, _, w)| AccountMgrSource::Privs {
privileges: vec![UserPrivilegeType::Usage],
level: AccountMgrLevel::Warehouse(w.to_string()),
},
);

rule!(
#role : "ROLE <role_name>"
| #warehouse_all_privs: "ALL [ PRIVILEGES ] ON WAREHOUSE <warehouse_name>"
| #udf_privs: "USAGE ON UDF <udf_name>"
| #warehouse_privs: "USAGE ON WAREHOUSE <warehouse_name>"
| #privs : "<privileges> ON <privileges_level>"
| #stage_privs : "<stage_privileges> ON STAGE <stage_name>"
| #udf_all_privs: "ALL [ PRIVILEGES ] ON UDF <udf_name>"
Expand Down Expand Up @@ -3229,11 +3251,16 @@ pub fn grant_all_level(i: Input) -> IResult<AccountMgrLevel> {
let stage = map(rule! { STAGE ~ #ident}, |(_, stage_name)| {
AccountMgrLevel::Stage(stage_name.to_string())
});

let warehouse = map(rule! { WAREHOUSE ~ #ident}, |(_, w)| {
AccountMgrLevel::Warehouse(w.to_string())
});
rule!(
#global : "*.*"
| #db : "<database>.*"
| #table : "<database>.<table>"
| #stage : "STAGE <stage_name>"
| #warehouse : "WAREHOUSE <warehouse_name>"
)(i)
}

Expand Down
4 changes: 4 additions & 0 deletions src/query/ast/tests/it/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,10 @@ fn test_statement() {
r#"GRANT usage ON UDF a TO 'test-grant';"#,
r#"REVOKE usage ON UDF a FROM 'test-grant';"#,
r#"REVOKE all ON UDF a FROM 'test-grant';"#,
r#"GRANT all ON warehouse a TO role 'test-grant';"#,
r#"GRANT usage ON warehouse a TO role 'test-grant';"#,
r#"REVOKE usage ON warehouse a FROM role 'test-grant';"#,
r#"REVOKE all ON warehouse a FROM role 'test-grant';"#,
r#"SHOW GRANTS ON TABLE db1.tb1;"#,
r#"SHOW GRANTS ON DATABASE db;"#,
r#"UPDATE db1.tb1 set a = a + 1, b = 2 WHERE c > 3;"#,
Expand Down
88 changes: 88 additions & 0 deletions src/query/ast/tests/it/testdata/stmt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16075,6 +16075,94 @@ Revoke(
)


---------- Input ----------
GRANT all ON warehouse a TO role 'test-grant';
---------- Output ---------
GRANT USAGE ON WAREHOUSE a TO ROLE 'test-grant'
---------- AST ------------
Grant(
GrantStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
GRANT usage ON warehouse a TO role 'test-grant';
---------- Output ---------
GRANT USAGE ON WAREHOUSE a TO ROLE 'test-grant'
---------- AST ------------
Grant(
GrantStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
REVOKE usage ON warehouse a FROM role 'test-grant';
---------- Output ---------
REVOKE USAGE ON WAREHOUSE a FROM ROLE 'test-grant'
---------- AST ------------
Revoke(
RevokeStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
REVOKE all ON warehouse a FROM role 'test-grant';
---------- Output ---------
REVOKE USAGE ON WAREHOUSE a FROM ROLE 'test-grant'
---------- AST ------------
Revoke(
RevokeStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
SHOW GRANTS ON TABLE db1.tb1;
---------- Output ---------
Expand Down
9 changes: 5 additions & 4 deletions src/query/service/src/interpreters/access/privilege_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl PrivilegeAccess {
GrantObject::UDF(name) => OwnershipObject::UDF {
name: name.to_string(),
},
GrantObject::Global => return Ok(None),
GrantObject::Global | GrantObject::Warehouse(_) => return Ok(None),
};

Ok(Some(object))
Expand Down Expand Up @@ -225,7 +225,7 @@ impl PrivilegeAccess {

return Err(ErrorCode::PermissionDenied(format!(
"Permission denied: privilege [{:?}] is required on '{}'.'{}'.* for user {} with roles [{}]. \
Note: Please ensure that your current role have the appropriate permissions to create a new Database|Table|UDF|Stage.",
Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage.",
privileges,
catalog_name,
db_name,
Expand Down Expand Up @@ -441,7 +441,7 @@ impl PrivilegeAccess {
| GrantObject::UDF(_)
| GrantObject::Stage(_)
| GrantObject::TableById(_, _, _) => true,
GrantObject::Global => false,
GrantObject::Global | GrantObject::Warehouse(_) => false,
};

if verify_ownership
Expand Down Expand Up @@ -490,11 +490,12 @@ impl PrivilegeAccess {
GrantObject::DatabaseById(_, _) => Err(ErrorCode::PermissionDenied("")),
GrantObject::Global
| GrantObject::UDF(_)
| GrantObject::Warehouse(_)
| GrantObject::Stage(_)
| GrantObject::Database(_, _)
| GrantObject::Table(_, _, _) => Err(ErrorCode::PermissionDenied(format!(
"Permission denied: privilege [{:?}] is required on {} for user {} with roles [{}]. \
Note: Please ensure that your current role have the appropriate permissions to create a new Database|Table|UDF|Stage.",
Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage.",
privilege,
grant_object,
&current_user.identity().display(),
Expand Down
Loading
Loading