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

fix: allow the object owner to grant and revoke privileges (#15519) #15527

Merged
merged 1 commit into from
Mar 8, 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
2 changes: 1 addition & 1 deletion src/frontend/src/handler/handle_privilege.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ pub async fn handle_revoke_privilege(
.revoke_privilege(
users,
privileges,
granted_by_id,
granted_by_id.unwrap_or(session.user_id()),
session.user_id(),
revoke_grant_option,
cascade,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ impl UserInfoWriter for MockUserInfoWriter {
&self,
users: Vec<UserId>,
privileges: Vec<GrantPrivilege>,
_granted_by: Option<UserId>,
_granted_by: UserId,
_revoke_by: UserId,
revoke_grant_option: bool,
_cascade: bool,
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/src/user/user_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub trait UserInfoWriter: Send + Sync {
&self,
users: Vec<UserId>,
privileges: Vec<GrantPrivilege>,
granted_by: Option<UserId>,
granted_by: UserId,
revoke_by: UserId,
revoke_grant_option: bool,
cascade: bool,
Expand Down Expand Up @@ -109,7 +109,7 @@ impl UserInfoWriter for UserInfoWriterImpl {
&self,
users: Vec<UserId>,
privileges: Vec<GrantPrivilege>,
granted_by: Option<UserId>,
granted_by: UserId,
revoke_by: UserId,
revoke_grant_option: bool,
cascade: bool,
Expand Down
2 changes: 1 addition & 1 deletion src/meta/service/src/user_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ impl UserService for UserServiceImpl {
.revoke_privilege(
user_ids,
&privileges,
Some(req.granted_by as _),
req.granted_by as _,
req.revoke_by as _,
req.revoke_grant_option,
req.cascade,
Expand Down
31 changes: 18 additions & 13 deletions src/meta/src/controller/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use sea_orm::{
use crate::controller::catalog::CatalogController;
use crate::controller::utils::{
check_user_name_duplicate, ensure_privileges_not_referred, ensure_user_id,
extract_grant_obj_id, get_referring_privileges_cascade, get_user_privilege,
extract_grant_obj_id, get_object_owner, get_referring_privileges_cascade, get_user_privilege,
list_user_info_by_ids, PartialUserPrivilege,
};
use crate::manager::{NotificationVersion, IGNORED_NOTIFICATION_VERSION};
Expand Down Expand Up @@ -248,6 +248,9 @@ impl CatalogController {
.ok_or_else(|| MetaError::catalog_id_not_found("user", grantor))?;
if !user.is_super {
for privilege in &mut privileges {
if grantor == get_object_owner(*privilege.oid.as_ref(), &txn).await? {
continue;
}
let filter = user_privilege::Column::UserId
.eq(grantor)
.and(user_privilege::Column::Oid.eq(*privilege.oid.as_ref()))
Expand Down Expand Up @@ -311,7 +314,7 @@ impl CatalogController {
&self,
user_ids: Vec<UserId>,
revoke_grant_privileges: &[PbGrantPrivilege],
granted_by: Option<UserId>,
granted_by: UserId,
revoke_by: UserId,
revoke_grant_option: bool,
cascade: bool,
Expand All @@ -327,7 +330,6 @@ impl CatalogController {
.await?
.ok_or_else(|| MetaError::catalog_id_not_found("user", revoke_by))?;

let granted_by = granted_by.unwrap_or(revoke_by);
// check whether user can revoke the privilege.
if !revoke_user.is_super && granted_by != revoke_by {
let granted_user_name: String = User::find_by_id(granted_by)
Expand Down Expand Up @@ -355,8 +357,11 @@ impl CatalogController {
}

let filter = if !revoke_user.is_super {
// ensure user have grant options.
// ensure user have grant options or is owner of the object.
for (obj, actions) in &revoke_items {
if revoke_by == get_object_owner(*obj, &txn).await? {
continue;
}
let owned_actions: HashSet<Action> = UserPrivilege::find()
.select_only()
.column(user_privilege::Column::Action)
Expand Down Expand Up @@ -386,11 +391,11 @@ impl CatalogController {
user_privilege::Column::UserId.is_in(user_ids.clone())
};
let mut root_user_privileges: Vec<PartialUserPrivilege> = vec![];
for (obj, actions) in &revoke_items {
for (obj, actions) in revoke_items {
let filter = filter
.clone()
.and(user_privilege::Column::Oid.eq(*obj))
.and(user_privilege::Column::Action.is_in(actions.clone()));
.and(user_privilege::Column::Oid.eq(obj))
.and(user_privilege::Column::Action.is_in(actions));
root_user_privileges.extend(
UserPrivilege::find()
.select_only()
Expand Down Expand Up @@ -588,7 +593,7 @@ mod tests {
mgr.revoke_privilege(
vec![user_1.user_id],
&[conn_with_option.clone()],
Some(TEST_ROOT_USER_ID),
TEST_ROOT_USER_ID,
user_2.user_id,
false,
false
Expand All @@ -603,7 +608,7 @@ mod tests {
mgr.revoke_privilege(
vec![user_2.user_id],
&[create_without_option.clone()],
None,
user_1.user_id,
user_1.user_id,
false,
false
Expand All @@ -618,7 +623,7 @@ mod tests {
mgr.revoke_privilege(
vec![user_1.user_id],
&[conn_with_option.clone()],
None,
TEST_ROOT_USER_ID,
TEST_ROOT_USER_ID,
false,
false
Expand All @@ -632,7 +637,7 @@ mod tests {
mgr.revoke_privilege(
vec![user_1.user_id],
&[create_without_option.clone()],
None,
TEST_ROOT_USER_ID,
TEST_ROOT_USER_ID,
false,
false,
Expand All @@ -649,7 +654,7 @@ mod tests {
mgr.revoke_privilege(
vec![user_1.user_id],
&[conn_with_option.clone()],
None,
TEST_ROOT_USER_ID,
TEST_ROOT_USER_ID,
true,
true,
Expand All @@ -672,7 +677,7 @@ mod tests {
mgr.revoke_privilege(
vec![user_1.user_id],
&[conn_with_option.clone()],
None,
TEST_ROOT_USER_ID,
TEST_ROOT_USER_ID,
false,
true,
Expand Down
15 changes: 15 additions & 0 deletions src/meta/src/controller/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,21 @@ where
Ok(user_infos)
}

/// `get_object_owner` returns the owner of the given object.
pub async fn get_object_owner<C>(object_id: ObjectId, db: &C) -> MetaResult<UserId>
where
C: ConnectionTrait,
{
let obj_owner: UserId = Object::find_by_id(object_id)
.select_only()
.column(object::Column::OwnerId)
.into_tuple()
.one(db)
.await?
.ok_or_else(|| MetaError::catalog_id_not_found("object", object_id))?;
Ok(obj_owner)
}

/// `construct_privilege_dependency_query` constructs a query to find all privileges that are dependent on the given one.
///
/// # Examples
Expand Down
44 changes: 43 additions & 1 deletion src/meta/src/manager/catalog/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ use risingwave_pb::catalog::{
StreamJobStatus, Table, View,
};
use risingwave_pb::data::DataType;
use risingwave_pb::user::grant_privilege::PbObject;

use super::{ConnectionId, DatabaseId, FunctionId, RelationId, SchemaId, SinkId, SourceId, ViewId};
use crate::manager::{IndexId, MetaSrvEnv, TableId};
use crate::manager::{IndexId, MetaSrvEnv, TableId, UserId};
use crate::model::MetadataModel;
use crate::{MetaError, MetaResult};

Expand Down Expand Up @@ -571,4 +572,45 @@ impl DatabaseManager {
))
}
}

pub fn get_object_owner(&self, object: &PbObject) -> MetaResult<UserId> {
match object {
PbObject::DatabaseId(id) => self
.databases
.get(id)
.map(|d| d.owner)
.ok_or_else(|| MetaError::catalog_id_not_found("database", id)),
PbObject::SchemaId(id) => self
.schemas
.get(id)
.map(|s| s.owner)
.ok_or_else(|| MetaError::catalog_id_not_found("schema", id)),
PbObject::TableId(id) => self
.tables
.get(id)
.map(|t| t.owner)
.ok_or_else(|| MetaError::catalog_id_not_found("table", id)),
PbObject::SourceId(id) => self
.sources
.get(id)
.map(|s| s.owner)
.ok_or_else(|| MetaError::catalog_id_not_found("source", id)),
PbObject::SinkId(id) => self
.sinks
.get(id)
.map(|s| s.owner)
.ok_or_else(|| MetaError::catalog_id_not_found("sink", id)),
PbObject::ViewId(id) => self
.views
.get(id)
.map(|v| v.owner)
.ok_or_else(|| MetaError::catalog_id_not_found("view", id)),
PbObject::FunctionId(id) => self
.functions
.get(id)
.map(|f| f.owner)
.ok_or_else(|| MetaError::catalog_id_not_found("function", id)),
_ => unreachable!("unexpected object type: {:?}", object),
}
}
}
44 changes: 37 additions & 7 deletions src/meta/src/manager/catalog/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3581,14 +3581,28 @@ impl CatalogManager {
true
}

/// Check whether the user is the owner of the object.
#[inline(always)]
fn check_owner(
database_manager: &DatabaseManager,
object: &Object,
user_id: UserId,
) -> MetaResult<bool> {
database_manager
.get_object_owner(object)
.map(|owner_id| owner_id == user_id)
}

pub async fn grant_privilege(
&self,
user_ids: &[UserId],
new_grant_privileges: &[GrantPrivilege],
grantor: UserId,
) -> MetaResult<NotificationVersion> {
let core = &mut self.core.lock().await.user;
let mut users = BTreeMapTransaction::new(&mut core.user_info);
let core = &mut *self.core.lock().await;
let user_core = &mut core.user;
let catalog_core = &core.database;
let mut users = BTreeMapTransaction::new(&mut user_core.user_info);
let mut user_updated = Vec::with_capacity(user_ids.len());
let grantor_info = users
.get(&grantor)
Expand All @@ -3607,6 +3621,13 @@ impl CatalogManager {
}
if !grantor_info.is_super {
for new_grant_privilege in new_grant_privileges {
if Self::check_owner(
catalog_core,
new_grant_privilege.object.as_ref().unwrap(),
grantor,
)? {
continue;
}
if let Some(privilege) = grantor_info
.grant_privileges
.iter()
Expand Down Expand Up @@ -3642,7 +3663,7 @@ impl CatalogManager {

commit_meta!(self, users)?;

let grant_user = core
let grant_user = user_core
.user_grant_relation
.entry(grantor)
.or_insert_with(HashSet::new);
Expand Down Expand Up @@ -3703,8 +3724,10 @@ impl CatalogManager {
revoke_grant_option: bool,
cascade: bool,
) -> MetaResult<NotificationVersion> {
let core = &mut self.core.lock().await.user;
let mut users = BTreeMapTransaction::new(&mut core.user_info);
let core = &mut *self.core.lock().await;
let user_core = &mut core.user;
let catalog_core = &core.database;
let mut users = BTreeMapTransaction::new(&mut user_core.user_info);
let mut user_updated = HashMap::new();
let mut users_info: VecDeque<UserInfo> = VecDeque::new();
let mut visited = HashSet::new();
Expand All @@ -3715,6 +3738,13 @@ impl CatalogManager {
let same_user = granted_by == revoke_by.id;
if !revoke_by.is_super {
for privilege in revoke_grant_privileges {
if Self::check_owner(
catalog_core,
privilege.object.as_ref().unwrap(),
revoke_by.id,
)? {
continue;
}
if let Some(user_privilege) = revoke_by
.grant_privileges
.iter()
Expand Down Expand Up @@ -3750,7 +3780,7 @@ impl CatalogManager {
}
while !users_info.is_empty() {
let mut cur_user = users_info.pop_front().unwrap();
let cur_relations = core
let cur_relations = user_core
.user_grant_relation
.get(&cur_user.id)
.cloned()
Expand Down Expand Up @@ -3805,7 +3835,7 @@ impl CatalogManager {

// Since we might revoke privileges recursively, just simply re-build the grant relation
// map here.
core.build_grant_relation_map();
user_core.build_grant_relation_map();

let mut version = 0;
// FIXME: user might not be updated.
Expand Down
26 changes: 26 additions & 0 deletions src/meta/src/manager/catalog/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ impl UserManager {
#[cfg(test)]
mod tests {
use risingwave_common::catalog::{DEFAULT_SUPER_USER, DEFAULT_SUPER_USER_ID};
use risingwave_pb::catalog::PbTable;
use risingwave_pb::user::grant_privilege::{Action, ActionWithGrantOption, Object};
use risingwave_pb::user::GrantPrivilege;

Expand Down Expand Up @@ -190,6 +191,31 @@ mod tests {
let users = catalog_manager.list_users().await;
assert_eq!(users.len(), 4);

let table = PbTable {
id: 0,
name: "t1".to_string(),
owner: DEFAULT_SUPER_USER_ID,
..Default::default()
};
let other_table = PbTable {
id: 1,
name: "t2".to_string(),
owner: DEFAULT_SUPER_USER_ID,
..Default::default()
};
catalog_manager
.start_create_table_procedure(&table, vec![])
.await?;
catalog_manager
.finish_create_table_procedure(vec![], table)
.await?;
catalog_manager
.start_create_table_procedure(&other_table, vec![])
.await?;
catalog_manager
.finish_create_table_procedure(vec![], other_table)
.await?;

let object = Object::TableId(0);
let other_object = Object::TableId(1);
// Grant when grantor does not have privilege.
Expand Down
3 changes: 1 addition & 2 deletions src/rpc_client/src/meta_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,12 +647,11 @@ impl MetaClient {
&self,
user_ids: Vec<u32>,
privileges: Vec<GrantPrivilege>,
granted_by: Option<u32>,
granted_by: u32,
revoke_by: u32,
revoke_grant_option: bool,
cascade: bool,
) -> Result<u64> {
let granted_by = granted_by.unwrap_or_default();
let request = RevokePrivilegeRequest {
user_ids,
privileges,
Expand Down
Loading