Skip to content

Commit 91809b4

Browse files
authored
fix(query): support table index privilege checks (#20124)
fix(query): relax table index privilege checks Allow table index create, refresh, and drop operations with target table ALTER access or ownership, while preserving GLOBAL SUPER as a compatibility fallback. Document the scoped table-index privilege model and add RBAC coverage for denied, ALTER, database owner, and SUPER cases.
1 parent b9e77b6 commit 91809b4

5 files changed

Lines changed: 334 additions & 5 deletions

File tree

src/query/service/src/interpreters/access/privilege_access.rs

Lines changed: 161 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,154 @@ impl PrivilegeAccess {
503503
Ok(())
504504
}
505505

506+
async fn validate_table_index_access(
507+
&self,
508+
catalog_name: &str,
509+
db_name: &str,
510+
table_name: &str,
511+
) -> Result<()> {
512+
self.access_system_history(
513+
Some(catalog_name),
514+
Some(db_name),
515+
None,
516+
UserPrivilegeType::Alter,
517+
)?;
518+
519+
self.validate_table_index_alter_or_super_access(catalog_name, db_name, table_name)
520+
.await
521+
}
522+
523+
async fn validate_drop_table_index_access(
524+
&self,
525+
catalog_name: &str,
526+
db_name: &str,
527+
table_name: &str,
528+
) -> Result<()> {
529+
self.access_system_history(
530+
Some(catalog_name),
531+
Some(db_name),
532+
None,
533+
UserPrivilegeType::Drop,
534+
)?;
535+
536+
match self
537+
.validate_table_index_alter_or_super_access(catalog_name, db_name, table_name)
538+
.await
539+
{
540+
Ok(()) => Ok(()),
541+
Err(err) if err.code() == ErrorCode::PERMISSION_DENIED => {
542+
match self
543+
.validate_access(&GrantObject::Global, UserPrivilegeType::Drop, false, false)
544+
.await
545+
{
546+
Ok(()) => Ok(()),
547+
Err(drop_err) if drop_err.code() == ErrorCode::PERMISSION_DENIED => Err(err),
548+
Err(drop_err) => Err(drop_err),
549+
}
550+
}
551+
Err(err) => Err(err),
552+
}
553+
}
554+
555+
async fn validate_table_index_alter_or_super_access(
556+
&self,
557+
catalog_name: &str,
558+
db_name: &str,
559+
table_name: &str,
560+
) -> Result<()> {
561+
match self
562+
.validate_real_table_alter_access(catalog_name, db_name, table_name)
563+
.await
564+
{
565+
Ok(()) => Ok(()),
566+
Err(err) if err.code() == ErrorCode::PERMISSION_DENIED => {
567+
match self
568+
.validate_access(&GrantObject::Global, UserPrivilegeType::Super, false, false)
569+
.await
570+
{
571+
Ok(()) => Ok(()),
572+
Err(super_err) if super_err.code() == ErrorCode::PERMISSION_DENIED => Err(err),
573+
Err(super_err) => Err(super_err),
574+
}
575+
}
576+
Err(err) => Err(err),
577+
}
578+
}
579+
580+
async fn validate_real_table_alter_access(
581+
&self,
582+
catalog_name: &str,
583+
db_name: &str,
584+
table_name: &str,
585+
) -> Result<()> {
586+
if self.ctx.is_temp_table(catalog_name, db_name, table_name) {
587+
return Ok(());
588+
}
589+
590+
let tenant = self.ctx.get_tenant();
591+
let catalog = self.ctx.get_catalog(catalog_name).await?;
592+
593+
match self
594+
.validate_access(
595+
&GrantObject::Table(
596+
catalog_name.to_string(),
597+
db_name.to_string(),
598+
table_name.to_string(),
599+
),
600+
UserPrivilegeType::Alter,
601+
false,
602+
false,
603+
)
604+
.await
605+
{
606+
Ok(()) => Ok(()),
607+
Err(err) if err.code() == ErrorCode::PERMISSION_DENIED => {
608+
match self
609+
.convert_to_id(&tenant, &catalog, db_name, Some(table_name), false)
610+
.await
611+
{
612+
Ok(ObjectId::Table(db_id, table_id)) => {
613+
match self
614+
.validate_access(
615+
&GrantObject::TableById(catalog_name.to_string(), db_id, table_id),
616+
UserPrivilegeType::Alter,
617+
false,
618+
false,
619+
)
620+
.await
621+
{
622+
Ok(()) => Ok(()),
623+
Err(err) if err.code() == ErrorCode::PERMISSION_DENIED => {
624+
let current_user = self.ctx.get_current_user()?;
625+
let session = self.ctx.get_current_session();
626+
let roles_name = session
627+
.get_all_effective_roles()
628+
.await?
629+
.iter()
630+
.map(|r| r.name.clone())
631+
.collect::<Vec<_>>()
632+
.join(",");
633+
Err(ErrorCode::PermissionDenied(format!(
634+
"Permission denied: privilege [{:?}] is required on '{}'.'{}'.'{}' for user {} with roles [{}]",
635+
UserPrivilegeType::Alter,
636+
catalog_name,
637+
db_name,
638+
table_name,
639+
&current_user.identity().display(),
640+
roles_name,
641+
)))
642+
}
643+
Err(err) => Err(err),
644+
}
645+
}
646+
Ok(ObjectId::Database(_)) => unreachable!("table name is provided"),
647+
Err(err) => Err(err.add_message("error on validating table index access")),
648+
}
649+
}
650+
Err(err) => Err(err),
651+
}
652+
}
653+
506654
async fn validate_warehouse_ownership(
507655
&self,
508656
warehouse: String,
@@ -1403,13 +1551,15 @@ impl AccessChecker for PrivilegeAccess {
14031551
Plan::DropDatabase(plan) => {
14041552
self.validate_db_access(&plan.catalog, &plan.database, UserPrivilegeType::Drop, plan.if_exists).await?;
14051553
}
1406-
Plan::UndropDatabase(_)
1407-
| Plan::DropIndex(_)
1408-
| Plan::DropTableIndex(_) => {
1554+
Plan::UndropDatabase(_) | Plan::DropIndex(_) => {
14091555
// undroptable/db need convert name to id. But because of drop, can not find the id. Upgrade Object to Database.
14101556
self.validate_access(&GrantObject::Global, UserPrivilegeType::Drop, false, false)
14111557
.await?;
14121558
}
1559+
Plan::DropTableIndex(plan) => {
1560+
self.validate_drop_table_index_access(&plan.catalog, &plan.database, &plan.table)
1561+
.await?;
1562+
}
14131563
Plan::CreateStage(_) => {
14141564
self.validate_access(&GrantObject::Global, UserPrivilegeType::Super, true, false)
14151565
.await?;
@@ -1893,12 +2043,15 @@ impl AccessChecker for PrivilegeAccess {
18932043
| Plan::RevertTable(_)
18942044
| Plan::AlterUDF(_)
18952045
| Plan::RefreshIndex(_)
1896-
| Plan::RefreshTableIndex(_)
18972046
| Plan::AlterRole(_)
18982047
| Plan::AlterUser(_) => {
18992048
self.validate_access(&GrantObject::Global, UserPrivilegeType::Alter, false, false)
19002049
.await?;
19012050
}
2051+
Plan::RefreshTableIndex(plan) => {
2052+
self.validate_table_index_access(&plan.catalog, &plan.database, &plan.table)
2053+
.await?;
2054+
}
19022055
Plan::CopyIntoTable(plan) => {
19032056
self.validate_stage_access(&plan.stage_table_info.stage_info, UserPrivilegeType::Read).await?;
19042057
self.validate_table_access(plan.catalog_info.catalog_name(), &plan.database_name, &plan.table_name, UserPrivilegeType::Insert, false, false).await?;
@@ -1998,7 +2151,6 @@ impl AccessChecker for PrivilegeAccess {
19982151
| Plan::DropPasswordPolicy(_)
19992152
| Plan::DescPasswordPolicy(_)
20002153
| Plan::CreateIndex(_)
2001-
| Plan::CreateTableIndex(_)
20022154
| Plan::CreateNotification(_)
20032155
| Plan::DropNotification(_)
20042156
| Plan::DescNotification(_)
@@ -2018,6 +2170,10 @@ impl AccessChecker for PrivilegeAccess {
20182170
self.validate_access(&GrantObject::Global, UserPrivilegeType::Super, false, false)
20192171
.await?;
20202172
}
2173+
Plan::CreateTableIndex(plan) => {
2174+
self.validate_table_index_access(&plan.catalog, &plan.database, &plan.table)
2175+
.await?;
2176+
}
20212177
Plan::CreateDatamaskPolicy(_) => {
20222178
self
20232179
.validate_access(

src/query/sql/src/planner/binder/ddl/index.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ impl Binder {
547547
index_type: *index_type,
548548
create_option: create_option.clone().into(),
549549
catalog,
550+
database,
551+
table: table.name().to_string(),
550552
index_name,
551553
column_ids,
552554
table_id,
@@ -923,6 +925,8 @@ impl Binder {
923925
index_type: *index_type,
924926
if_exists: *if_exists,
925927
catalog,
928+
database,
929+
table: table.name().to_string(),
926930
index_name,
927931
table_id,
928932
};

src/query/sql/src/planner/plans/ddl/index.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ pub struct CreateTableIndexPlan {
6565
pub index_type: TableIndexType,
6666
pub create_option: CreateOption,
6767
pub catalog: String,
68+
pub database: String,
69+
pub table: String,
6870
pub index_name: String,
6971
pub column_ids: Vec<ColumnId>,
7072
pub table_id: MetaId,
@@ -78,6 +80,8 @@ pub struct DropTableIndexPlan {
7880
pub index_type: TableIndexType,
7981
pub if_exists: bool,
8082
pub catalog: String,
83+
pub database: String,
84+
pub table: String,
8185
pub index_name: String,
8286
pub table_id: MetaId,
8387
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== denied table index ===
2+
1
3+
=== table function name collision ===
4+
1
5+
=== alter ngram index ===
6+
alter ok
7+
=== table owner inverted index ===
8+
0
9+
table owner ok
10+
=== db owner vector index ===
11+
db owner ok
12+
=== super table index ===
13+
super ok
14+
=== global drop table index ===
15+
global drop ok
16+
=== system_history hard deny for super ===
17+
1

0 commit comments

Comments
 (0)