Skip to content

chore: use Usage privilege optimize USEDB Plan #17679

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

Merged
merged 3 commits into from
Apr 9, 2025
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
4 changes: 3 additions & 1 deletion src/meta/app/src/principal/user_privilege.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ impl UserPrivilegeSet {
/// Currently the privileges available to a database and a table are the same, it might becomes
/// some differences in the future.
pub fn available_privileges_on_database(available_ownership: bool) -> Self {
UserPrivilegeSet::available_privileges_on_table(available_ownership)
(UserPrivilegeSet::available_privileges_on_table(available_ownership).privileges
| make_bitflags!(UserPrivilegeType::{ Usage }))
.into()
}

/// The all privileges global which available to the table object
Expand Down
46 changes: 36 additions & 10 deletions src/query/service/src/interpreters/access/privilege_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ impl AccessChecker for PrivilegeAccess {
ObjectId::Database(db_id) => { (db_id, None) }
};

if has_priv(&tenant, database, None, show_db_id, table_id, grant_set).await? {
if has_priv(&tenant, database, None, show_db_id, table_id, grant_set, false).await? {
return Ok(())
}

Expand All @@ -777,7 +777,7 @@ impl AccessChecker for PrivilegeAccess {
ObjectId::Table(db_id, table_id) => { (db_id, Some(table_id)) }
ObjectId::Database(db_id) => { (db_id, None) }
};
if has_priv(&tenant, database, None, show_db_id, table_id, grant_set).await? {
if has_priv(&tenant, database, None, show_db_id, table_id, grant_set, true).await? {
return Ok(());
}
let user_api = UserApiProvider::instance();
Expand All @@ -803,7 +803,7 @@ impl AccessChecker for PrivilegeAccess {
ObjectId::Table(db_id, table_id) => { (db_id, Some(table_id)) }
ObjectId::Database(db_id) => { (db_id, None) }
};
let has_priv = has_priv(&tenant, database, Some(table), db_id, table_id, grant_set).await?;
let has_priv = has_priv(&tenant, database, Some(table), db_id, table_id, grant_set, false).await?;
return if has_priv {
Ok(())
} else {
Expand Down Expand Up @@ -932,7 +932,7 @@ impl AccessChecker for PrivilegeAccess {
ObjectId::Table(db_id, table_id) => { (db_id, Some(table_id)) }
ObjectId::Database(db_id) => { (db_id, None) }
};
if has_priv(&tenant, &plan.database, None, show_db_id, None, grant_set).await? {
if has_priv(&tenant, &plan.database, None, show_db_id, None, grant_set, true).await? {
return Ok(());
}
let user_api = UserApiProvider::instance();
Expand Down Expand Up @@ -1434,6 +1434,7 @@ async fn has_priv(
db_id: u64,
table_id: Option<u64>,
grant_set: UserGrantSet,
valid_usage_priv: bool,
) -> Result<bool> {
if db_name.to_lowercase() == "information_schema" {
return Ok(true);
Expand Down Expand Up @@ -1461,13 +1462,38 @@ async fn has_priv(
if db_name.to_lowercase() == "system" {
return true;
}
e.privileges().iter().any(|privilege| {
UserPrivilegeSet::available_privileges_on_database(false)
.has_privilege(privilege)
})
if valid_usage_priv {
e.privileges().iter().any(|privilege| {
UserPrivilegeSet::available_privileges_on_database(false)
.has_privilege(privilege)
})
} else {
!(e.privileges().len() == 1
&& e.privileges().contains(UserPrivilegeType::Usage))
&& e.privileges().iter().any(|privilege| {
UserPrivilegeSet::available_privileges_on_database(false)
.has_privilege(privilege)
})
}
}
GrantObject::Database(_, ldb) => {
if valid_usage_priv {
*ldb == db_name
} else {
!(e.privileges().len() == 1
&& e.privileges().contains(UserPrivilegeType::Usage))
&& *ldb == db_name
}
}
GrantObject::DatabaseById(_, ldb) => {
if valid_usage_priv {
*ldb == db_id
} else {
!(e.privileges().len() == 1
&& e.privileges().contains(UserPrivilegeType::Usage))
&& *ldb == db_id
}
}
GrantObject::Database(_, ldb) => *ldb == db_name,
GrantObject::DatabaseById(_, ldb) => *ldb == db_id,
GrantObject::Table(_, ldb, ltab) => {
if let Some(table) = table_name {
*ldb == db_name && *ltab == table
Expand Down
13 changes: 11 additions & 2 deletions src/query/users/src/visibility_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,19 @@ impl GrantObjectVisibilityChecker {
);
}
GrantObject::Database(catalog, db) => {
granted_databases.insert((catalog.to_string(), db.to_string()));
if !(ent.privileges().len() == 1
&& ent.privileges().contains(UserPrivilegeType::Usage))
{
granted_databases.insert((catalog.to_string(), db.to_string()));
}
}
GrantObject::DatabaseById(catalog, db) => {
granted_databases_id.insert((catalog.to_string(), *db));
// If only has db level usage privilege means only support use db.
if !(ent.privileges().len() == 1
&& ent.privileges().contains(UserPrivilegeType::Usage))
{
granted_databases_id.insert((catalog.to_string(), *db));
}
}
GrantObject::Table(catalog, db, table) => {
granted_tables.insert((
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ OWNERSHIP s1 NULL ROLE account_admin (empty)
query TT
select * EXCLUDE(object_id) from show_grants('database', 'db1', 'default');
----
CREATE,SELECT,INSERT,UPDATE,DELETE,DROP,ALTER,GRANT db1 ROLE role2 (empty)
CREATE,SELECT,INSERT,UPDATE,DELETE,DROP,ALTER,GRANT db1 ROLE role3 (empty)
USAGE,CREATE,SELECT,INSERT,UPDATE,DELETE,DROP,ALTER,GRANT db1 ROLE role2 (empty)
USAGE,CREATE,SELECT,INSERT,UPDATE,DELETE,DROP,ALTER,GRANT db1 ROLE role3 (empty)
OWNERSHIP db1 ROLE account_admin (empty)

query TT
Expand Down
15 changes: 15 additions & 0 deletions tests/suites/0_stateless/18_rbac/18_0014_use_db_priv_check.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== show db ===
a
b
information_schema
system
=== use db ===
=== show tables from a ===
t1
=== show tables from b ===
t
=== show columns ===
Error: APIError: QueryFailed: [1063]Permission denied: User 'test'@'%' does not have the required privileges for table 'a.t'
ida1 INT YES NULL NULL
idb INT YES NULL NULL
Error: APIError: QueryFailed: [1063]Permission denied: User 'test'@'%' does not have the required privileges for table 'b.t1'
56 changes: 56 additions & 0 deletions tests/suites/0_stateless/18_rbac/18_0014_use_db_priv_check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash

CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
. "$CURDIR"/../../../shell_env.sh


export TEST_USER_PASSWORD="password"
export USER_TEST_CONNECT="bendsql --user=test --password=password --host=${QUERY_MYSQL_HANDLER_HOST} --port ${QUERY_HTTP_HANDLER_PORT}"


echo "drop user if exists test" | $BENDSQL_CLIENT_CONNECT
echo "create user test identified by '$TEST_USER_PASSWORD'" | $BENDSQL_CLIENT_CONNECT

echo "create or replace database a" | $BENDSQL_CLIENT_CONNECT
echo "create or replace database b" | $BENDSQL_CLIENT_CONNECT
echo "create or replace database c" | $BENDSQL_CLIENT_CONNECT
echo "create or replace table a.t(ida int)" | $BENDSQL_CLIENT_CONNECT
echo "create or replace table a.t1(ida1 int)" | $BENDSQL_CLIENT_CONNECT
echo "create or replace table b.t(idb int)" | $BENDSQL_CLIENT_CONNECT
echo "create or replace table b.t1(idb1 int)" | $BENDSQL_CLIENT_CONNECT
echo "drop role if exists test_role1" | $BENDSQL_CLIENT_CONNECT
echo "drop role if exists test_role2" | $BENDSQL_CLIENT_CONNECT
echo "create role test_role1" | $BENDSQL_CLIENT_CONNECT
echo "create role test_role2" | $BENDSQL_CLIENT_CONNECT
echo "grant usage on a.* to role test_role1" | $BENDSQL_CLIENT_CONNECT
echo "grant usage on b.* to role test_role1" | $BENDSQL_CLIENT_CONNECT
echo "grant ownership on b.t to role test_role1" | $BENDSQL_CLIENT_CONNECT

echo "grant role test_role1 to test" | $BENDSQL_CLIENT_CONNECT
echo "grant select on a.t1 to test" | $BENDSQL_CLIENT_CONNECT

echo "=== show db ==="
echo "show databases" | $USER_TEST_CONNECT


echo "=== use db ==="
echo "use a" | $USER_TEST_CONNECT
echo "use b" | $USER_TEST_CONNECT

echo "=== show tables from a ==="
echo "show tables from a" | $USER_TEST_CONNECT # only display a.t1
echo "=== show tables from b ==="
echo "show tables from b" | $USER_TEST_CONNECT # only display b.t

echo "=== show columns ==="
echo "show columns from t from a" | $USER_TEST_CONNECT
echo "show columns from t1 from a" | $USER_TEST_CONNECT
echo "show columns from t from b" | $USER_TEST_CONNECT
echo "show columns from t1 from b" | $USER_TEST_CONNECT

echo "drop user if exists test" | $BENDSQL_CLIENT_CONNECT
echo "drop role if exists test_role1" | $BENDSQL_CLIENT_CONNECT
echo "drop role if exists test_role2" | $BENDSQL_CLIENT_CONNECT
echo "drop database a" | $BENDSQL_CLIENT_CONNECT
echo "drop database b" | $BENDSQL_CLIENT_CONNECT
echo "drop database c" | $BENDSQL_CLIENT_CONNECT