From e0b45202cc7d6a0802c3bc62bcb5c3b9cac85803 Mon Sep 17 00:00:00 2001 From: konac-hamza <1hamzakonac23@gmail.com> Date: Mon, 1 Dec 2025 00:31:23 +0300 Subject: [PATCH 1/3] feat: insert system assignment info into response and align with list method logic (#143) --- src/assignment/backend/sql/assignment/list.rs | 150 +++++++++++------- 1 file changed, 95 insertions(+), 55 deletions(-) diff --git a/src/assignment/backend/sql/assignment/list.rs b/src/assignment/backend/sql/assignment/list.rs index 5d941fc6..f3e15751 100644 --- a/src/assignment/backend/sql/assignment/list.rs +++ b/src/assignment/backend/sql/assignment/list.rs @@ -58,20 +58,17 @@ pub async fn list( .filter(db_assignment::Column::Type.is_in([ DbAssignmentType::UserProject, DbAssignmentType::GroupProject, - ])) - .filter(db_assignment::Column::Inherited.eq(false)); + ])); } else if let Some(val) = ¶ms.domain_id { select_assignment = select_assignment .filter(db_assignment::Column::TargetId.eq(val)) .filter( db_assignment::Column::Type .is_in([DbAssignmentType::UserDomain, DbAssignmentType::GroupDomain]), - ) - .filter(db_assignment::Column::Inherited.eq(false)); + ); } else { - select_system_assignment = select_system_assignment - .filter(db_system_assignment::Column::TargetId.eq("system")) - .filter(db_system_assignment::Column::Inherited.eq(false)); + select_system_assignment = + select_system_assignment.filter(db_system_assignment::Column::TargetId.eq("system")); } let results: Result, _> = if let Some(true) = ¶ms.include_names { @@ -130,21 +127,30 @@ pub async fn list( /// Get all role assignments by list of actors on list of targets. /// -/// It is a naive interpretation of the effective role assignments where we -/// check all roles assigned to the user (including groups) on a concrete target -/// (including all higher targets the role can be inherited from) +/// It is a naive interpretation of the effective role assignments where we check all roles +/// assigned to the user (including groups) on a concrete target (including all higher targets the +/// role can be inherited from) pub async fn list_for_multiple_actors_and_targets( _conf: &Config, db: &DatabaseConnection, params: &RoleAssignmentListForMultipleActorTargetParameters, ) -> Result, AssignmentDatabaseError> { let mut select = DbAssignment::find(); + let mut select_system = DbSystemAssignment::find(); + // flags of actors and role ID matches as boolean expressions + let include_system = params.targets.is_empty(); // Track if we should query system table if !params.actors.is_empty() { select = select.filter(db_assignment::Column::ActorId.is_in(params.actors.clone())); + if !include_system { + select_system = select_system.filter(db_system_assignment::Column::ActorId.is_in(params.actors.clone())); + } } if let Some(rid) = ¶ms.role_id { select = select.filter(db_assignment::Column::RoleId.eq(rid)); + if !include_system { + select_system = select_system.filter(db_system_assignment::Column::RoleId.eq(rid)); + } } if !params.targets.is_empty() { let mut cond = Condition::any(); @@ -161,48 +167,81 @@ pub async fn list_for_multiple_actors_and_targets( } select = select.filter(cond); } + if include_system { + select_system = select_system.filter(db_system_assignment::Column::TargetId.eq("system")); + } // Get all implied rules let imply_rules = implied_role::list_rules(db, true).await?; - let mut db_assignments: BTreeMap = BTreeMap::new(); - // Get assignments resolving the roles inference - for assignment in select.all(db).await.map_err(|err| { - db_err( - err, - "fetching role assignments for multiple actors and targets", - ) - })? { - db_assignments.insert(assignment.role_id.clone(), assignment.clone()); + // Step 1: Query and convert regular assignments to Assignment + let assignments: Vec = select + .all(db) + .await + .map_err(|err| db_err(err, "fetching role assignments"))? + .into_iter() + .map(TryInto::::try_into) + .collect::, _>>()?; + + // Step 2: Query and convert system assignments to Assignment + let system_assignments: Vec = if include_system { + select_system + .all(db) + .await + .map_err(|err| db_err(err, "fetching system assignments"))? + .into_iter() + .map(TryInto::::try_into) + .collect::, _>>()? + } else { + Vec::new() // Don't query system table if not needed + }; + + // Step 3: Merge both vectors + let all_assignments = assignments.into_iter().chain(system_assignments); + + // Step 4: Apply role implications on Assignment type + let mut result_map: BTreeMap = BTreeMap::new(); + + for assignment in all_assignments { + // Insert direct assignment + result_map.insert(assignment.role_id.clone(), assignment.clone()); + + // Apply role implications if let Some(implies) = imply_rules.get(&assignment.role_id) { - let mut implied_assignment = assignment.clone(); - for implied in implies.iter() { - implied_assignment.role_id = implied.clone(); - db_assignments.insert(implied.clone(), implied_assignment.clone()); + for implied_role_id in implies.iter() { + // Create implied assignment + let mut implied_assignment = assignment.clone(); + implied_assignment.role_id = implied_role_id.clone(); + // Note: role_name will be wrong (shows original role name) + // We'll fix this below + result_map.insert(implied_role_id.clone(), implied_assignment); } } } - if !db_assignments.is_empty() { - // Get roles for the found IDs - let roles: HashMap = HashMap::from_iter( - DbRole::find() - .select_only() - .columns([db_role::Column::Id, db_role::Column::Name]) - .filter(Expr::col(db_role::Column::Id).is_in(db_assignments.keys())) - .into_tuple() - .all(db) - .await - .map_err(|err| db_err(err, "fetching roles by ids"))?, - ); - let results: Result, _> = db_assignments - .values() - .map(|item| TryInto::::try_into((item, roles.get(&item.role_id)))) - .collect(); - results - } else { - Ok(Vec::new()) + // Step 5: Fetch role names for all roles (including implied) + if !result_map.is_empty() { + let roles: HashMap = HashMap::from_iter( + DbRole::find() + .select_only() + .columns([db_role::Column::Id, db_role::Column::Name]) + .filter(Expr::col(db_role::Column::Id).is_in(result_map.keys())) + .into_tuple() + .all(db) + .await + .map_err(|err| db_err(err, "fetching roles by ids"))?, + ); + + // Update role names in all assignments + for assignment in result_map.values_mut() { + if let Some(name) = roles.get(&assignment.role_id) { + assignment.role_name = Some(name.clone()); + } } + } + + // Step 6: Return results + Ok(result_map.into_values().collect()) } #[cfg(test)] @@ -257,8 +296,8 @@ mod tests { ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."target_id" = $1 AND "system_assignment"."inherited" = $2"#, - ["system".into(), false.into()] + r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."target_id" = $1"#, + ["system".into()] ), ] ); @@ -313,8 +352,8 @@ mod tests { ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."role_id" = $1 AND "system_assignment"."target_id" = $2 AND "system_assignment"."inherited" = $3"#, - ["1".into(), "system".into(), false.into()] + r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."role_id" = $1 AND "system_assignment"."target_id" = $2"#, + ["1".into(), "system".into()] ), ] ); @@ -352,13 +391,8 @@ mod tests { db.into_transaction_log(), [Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment" WHERE "assignment"."target_id" = $1 AND "assignment"."type" IN (CAST($2 AS "type"), CAST($3 AS "type")) AND "assignment"."inherited" = $4"#, - [ - "target".into(), - "UserProject".into(), - "GroupProject".into(), - false.into() - ] + r#"SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment" WHERE "assignment"."target_id" = $1 AND "assignment"."type" IN (CAST($2 AS "type"), CAST($3 AS "type"))"#, + ["target".into(), "UserProject".into(), "GroupProject".into()] ),] ); } @@ -412,8 +446,8 @@ mod tests { ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT "system_assignment"."type" AS "A_type", "system_assignment"."actor_id" AS "A_actor_id", "system_assignment"."target_id" AS "A_target_id", "system_assignment"."role_id" AS "A_role_id", "system_assignment"."inherited" AS "A_inherited", "role"."id" AS "B_id", "role"."name" AS "B_name", "role"."extra" AS "B_extra", "role"."domain_id" AS "B_domain_id", "role"."description" AS "B_description" FROM "system_assignment" LEFT JOIN "role" ON "system_assignment"."role_id" = "role"."id" WHERE "system_assignment"."target_id" = $1 AND "system_assignment"."inherited" = $2"#, - ["system".into(), false.into()] + r#"SELECT "system_assignment"."type" AS "A_type", "system_assignment"."actor_id" AS "A_actor_id", "system_assignment"."target_id" AS "A_target_id", "system_assignment"."role_id" AS "A_role_id", "system_assignment"."inherited" AS "A_inherited", "role"."id" AS "B_id", "role"."name" AS "B_name", "role"."extra" AS "B_extra", "role"."domain_id" AS "B_domain_id", "role"."description" AS "B_description" FROM "system_assignment" LEFT JOIN "role" ON "system_assignment"."role_id" = "role"."id" WHERE "system_assignment"."target_id" = $1"#, + ["system".into()] ), ] ); @@ -568,6 +602,7 @@ mod tests { let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([get_implied_rules_mock()]) .append_query_results([vec![get_role_assignment_mock("1")]]) + .append_query_results([vec![get_role_system_assignment_mock("2")]]) .append_query_results([vec![ get_role_mock("1", "rname"), get_role_mock("2", "rname2"), @@ -603,6 +638,11 @@ mod tests { r#"SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment""#, [] ), + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."target_id" = $1"#, + ["system".into()] + ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"SELECT "role"."id", "role"."name" FROM "role" WHERE "id" IN ($1, $2)"#, From 9a30eae719841878910a9747d4b1a842ec4a41a3 Mon Sep 17 00:00:00 2001 From: konac-hamza <1hamzakonac23@gmail.com> Date: Mon, 1 Dec 2025 00:36:13 +0300 Subject: [PATCH 2/3] Revert "feat: insert system assignment info into response and align with list method logic (#143)" This reverts commit e0b45202cc7d6a0802c3bc62bcb5c3b9cac85803. --- src/assignment/backend/sql/assignment/list.rs | 150 +++++++----------- 1 file changed, 55 insertions(+), 95 deletions(-) diff --git a/src/assignment/backend/sql/assignment/list.rs b/src/assignment/backend/sql/assignment/list.rs index f3e15751..5d941fc6 100644 --- a/src/assignment/backend/sql/assignment/list.rs +++ b/src/assignment/backend/sql/assignment/list.rs @@ -58,17 +58,20 @@ pub async fn list( .filter(db_assignment::Column::Type.is_in([ DbAssignmentType::UserProject, DbAssignmentType::GroupProject, - ])); + ])) + .filter(db_assignment::Column::Inherited.eq(false)); } else if let Some(val) = ¶ms.domain_id { select_assignment = select_assignment .filter(db_assignment::Column::TargetId.eq(val)) .filter( db_assignment::Column::Type .is_in([DbAssignmentType::UserDomain, DbAssignmentType::GroupDomain]), - ); + ) + .filter(db_assignment::Column::Inherited.eq(false)); } else { - select_system_assignment = - select_system_assignment.filter(db_system_assignment::Column::TargetId.eq("system")); + select_system_assignment = select_system_assignment + .filter(db_system_assignment::Column::TargetId.eq("system")) + .filter(db_system_assignment::Column::Inherited.eq(false)); } let results: Result, _> = if let Some(true) = ¶ms.include_names { @@ -127,30 +130,21 @@ pub async fn list( /// Get all role assignments by list of actors on list of targets. /// -/// It is a naive interpretation of the effective role assignments where we check all roles -/// assigned to the user (including groups) on a concrete target (including all higher targets the -/// role can be inherited from) +/// It is a naive interpretation of the effective role assignments where we +/// check all roles assigned to the user (including groups) on a concrete target +/// (including all higher targets the role can be inherited from) pub async fn list_for_multiple_actors_and_targets( _conf: &Config, db: &DatabaseConnection, params: &RoleAssignmentListForMultipleActorTargetParameters, ) -> Result, AssignmentDatabaseError> { let mut select = DbAssignment::find(); - let mut select_system = DbSystemAssignment::find(); - // flags of actors and role ID matches as boolean expressions - let include_system = params.targets.is_empty(); // Track if we should query system table if !params.actors.is_empty() { select = select.filter(db_assignment::Column::ActorId.is_in(params.actors.clone())); - if !include_system { - select_system = select_system.filter(db_system_assignment::Column::ActorId.is_in(params.actors.clone())); - } } if let Some(rid) = ¶ms.role_id { select = select.filter(db_assignment::Column::RoleId.eq(rid)); - if !include_system { - select_system = select_system.filter(db_system_assignment::Column::RoleId.eq(rid)); - } } if !params.targets.is_empty() { let mut cond = Condition::any(); @@ -167,81 +161,48 @@ pub async fn list_for_multiple_actors_and_targets( } select = select.filter(cond); } - if include_system { - select_system = select_system.filter(db_system_assignment::Column::TargetId.eq("system")); - } // Get all implied rules let imply_rules = implied_role::list_rules(db, true).await?; - // Step 1: Query and convert regular assignments to Assignment - let assignments: Vec = select - .all(db) - .await - .map_err(|err| db_err(err, "fetching role assignments"))? - .into_iter() - .map(TryInto::::try_into) - .collect::, _>>()?; - - // Step 2: Query and convert system assignments to Assignment - let system_assignments: Vec = if include_system { - select_system - .all(db) - .await - .map_err(|err| db_err(err, "fetching system assignments"))? - .into_iter() - .map(TryInto::::try_into) - .collect::, _>>()? - } else { - Vec::new() // Don't query system table if not needed - }; - - // Step 3: Merge both vectors - let all_assignments = assignments.into_iter().chain(system_assignments); - - // Step 4: Apply role implications on Assignment type - let mut result_map: BTreeMap = BTreeMap::new(); - - for assignment in all_assignments { - // Insert direct assignment - result_map.insert(assignment.role_id.clone(), assignment.clone()); - - // Apply role implications + let mut db_assignments: BTreeMap = BTreeMap::new(); + // Get assignments resolving the roles inference + for assignment in select.all(db).await.map_err(|err| { + db_err( + err, + "fetching role assignments for multiple actors and targets", + ) + })? { + db_assignments.insert(assignment.role_id.clone(), assignment.clone()); if let Some(implies) = imply_rules.get(&assignment.role_id) { - for implied_role_id in implies.iter() { - // Create implied assignment - let mut implied_assignment = assignment.clone(); - implied_assignment.role_id = implied_role_id.clone(); - // Note: role_name will be wrong (shows original role name) - // We'll fix this below - result_map.insert(implied_role_id.clone(), implied_assignment); + let mut implied_assignment = assignment.clone(); + for implied in implies.iter() { + implied_assignment.role_id = implied.clone(); + db_assignments.insert(implied.clone(), implied_assignment.clone()); } } } - // Step 5: Fetch role names for all roles (including implied) - if !result_map.is_empty() { - let roles: HashMap = HashMap::from_iter( - DbRole::find() - .select_only() - .columns([db_role::Column::Id, db_role::Column::Name]) - .filter(Expr::col(db_role::Column::Id).is_in(result_map.keys())) - .into_tuple() - .all(db) - .await - .map_err(|err| db_err(err, "fetching roles by ids"))?, - ); - - // Update role names in all assignments - for assignment in result_map.values_mut() { - if let Some(name) = roles.get(&assignment.role_id) { - assignment.role_name = Some(name.clone()); - } - } + if !db_assignments.is_empty() { + // Get roles for the found IDs + let roles: HashMap = HashMap::from_iter( + DbRole::find() + .select_only() + .columns([db_role::Column::Id, db_role::Column::Name]) + .filter(Expr::col(db_role::Column::Id).is_in(db_assignments.keys())) + .into_tuple() + .all(db) + .await + .map_err(|err| db_err(err, "fetching roles by ids"))?, + ); + let results: Result, _> = db_assignments + .values() + .map(|item| TryInto::::try_into((item, roles.get(&item.role_id)))) + .collect(); + results + } else { + Ok(Vec::new()) } - - // Step 6: Return results - Ok(result_map.into_values().collect()) } #[cfg(test)] @@ -296,8 +257,8 @@ mod tests { ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."target_id" = $1"#, - ["system".into()] + r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."target_id" = $1 AND "system_assignment"."inherited" = $2"#, + ["system".into(), false.into()] ), ] ); @@ -352,8 +313,8 @@ mod tests { ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."role_id" = $1 AND "system_assignment"."target_id" = $2"#, - ["1".into(), "system".into()] + r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."role_id" = $1 AND "system_assignment"."target_id" = $2 AND "system_assignment"."inherited" = $3"#, + ["1".into(), "system".into(), false.into()] ), ] ); @@ -391,8 +352,13 @@ mod tests { db.into_transaction_log(), [Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment" WHERE "assignment"."target_id" = $1 AND "assignment"."type" IN (CAST($2 AS "type"), CAST($3 AS "type"))"#, - ["target".into(), "UserProject".into(), "GroupProject".into()] + r#"SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment" WHERE "assignment"."target_id" = $1 AND "assignment"."type" IN (CAST($2 AS "type"), CAST($3 AS "type")) AND "assignment"."inherited" = $4"#, + [ + "target".into(), + "UserProject".into(), + "GroupProject".into(), + false.into() + ] ),] ); } @@ -446,8 +412,8 @@ mod tests { ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, - r#"SELECT "system_assignment"."type" AS "A_type", "system_assignment"."actor_id" AS "A_actor_id", "system_assignment"."target_id" AS "A_target_id", "system_assignment"."role_id" AS "A_role_id", "system_assignment"."inherited" AS "A_inherited", "role"."id" AS "B_id", "role"."name" AS "B_name", "role"."extra" AS "B_extra", "role"."domain_id" AS "B_domain_id", "role"."description" AS "B_description" FROM "system_assignment" LEFT JOIN "role" ON "system_assignment"."role_id" = "role"."id" WHERE "system_assignment"."target_id" = $1"#, - ["system".into()] + r#"SELECT "system_assignment"."type" AS "A_type", "system_assignment"."actor_id" AS "A_actor_id", "system_assignment"."target_id" AS "A_target_id", "system_assignment"."role_id" AS "A_role_id", "system_assignment"."inherited" AS "A_inherited", "role"."id" AS "B_id", "role"."name" AS "B_name", "role"."extra" AS "B_extra", "role"."domain_id" AS "B_domain_id", "role"."description" AS "B_description" FROM "system_assignment" LEFT JOIN "role" ON "system_assignment"."role_id" = "role"."id" WHERE "system_assignment"."target_id" = $1 AND "system_assignment"."inherited" = $2"#, + ["system".into(), false.into()] ), ] ); @@ -602,7 +568,6 @@ mod tests { let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([get_implied_rules_mock()]) .append_query_results([vec![get_role_assignment_mock("1")]]) - .append_query_results([vec![get_role_system_assignment_mock("2")]]) .append_query_results([vec![ get_role_mock("1", "rname"), get_role_mock("2", "rname2"), @@ -638,11 +603,6 @@ mod tests { r#"SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment""#, [] ), - Transaction::from_sql_and_values( - DatabaseBackend::Postgres, - r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."target_id" = $1"#, - ["system".into()] - ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"SELECT "role"."id", "role"."name" FROM "role" WHERE "id" IN ($1, $2)"#, From f6bd7099bae424034a427bb3c01eacb6f0a60985 Mon Sep 17 00:00:00 2001 From: konac-hamza Date: Sat, 6 Dec 2025 06:18:06 +0300 Subject: [PATCH 3/3] feat: Improve role assignment response structure Integrate RoleAssignmentTargetType and simplify queries. --- src/assignment/backend/sql/assignment/list.rs | 83 ++++++++++++++----- src/assignment/mod.rs | 6 ++ 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/assignment/backend/sql/assignment/list.rs b/src/assignment/backend/sql/assignment/list.rs index aaf174c1..5fe1b58a 100644 --- a/src/assignment/backend/sql/assignment/list.rs +++ b/src/assignment/backend/sql/assignment/list.rs @@ -139,16 +139,26 @@ pub async fn list_for_multiple_actors_and_targets( params: &RoleAssignmentListForMultipleActorTargetParameters, ) -> Result, AssignmentDatabaseError> { let mut select = DbAssignment::find(); + let mut select_system = DbSystemAssignment::find(); + // flags of actors and role ID matches as boolean expressions + let mut include_system = false; // Track if we should query system table if !params.actors.is_empty() { select = select.filter(db_assignment::Column::ActorId.is_in(params.actors.clone())); + select_system = select_system + .filter(db_system_assignment::Column::ActorId.is_in(params.actors.clone())); } if let Some(rid) = ¶ms.role_id { select = select.filter(db_assignment::Column::RoleId.eq(rid)); + select_system = select_system.filter(db_system_assignment::Column::RoleId.eq(rid)); } if !params.targets.is_empty() { let mut cond = Condition::any(); for target in params.targets.iter() { + if let RoleAssignmentTargetType::System = &target.r#type { + include_system = true; + continue; + } cond = cond.add( Condition::all() .add(db_assignment::Column::TargetId.eq(&target.id)) @@ -160,49 +170,70 @@ pub async fn list_for_multiple_actors_and_targets( ); } select = select.filter(cond); + } else { + include_system = true; } + // System query always filters by target_id = "system" + select_system = select_system.filter(db_system_assignment::Column::TargetId.eq("system")); // Get all implied rules let imply_rules = implied_role::list_rules(db, true).await?; - let mut db_assignments: BTreeMap = BTreeMap::new(); - // Get assignments resolving the roles inference - for assignment in select.all(db).await.map_err(|err| { - db_err( - err, - "fetching role assignments for multiple actors and targets", - ) - })? { - db_assignments.insert(assignment.role_id.clone(), assignment.clone()); + // Query both tables in parallel (if needed) + let (db_assignments, db_system_assignments) = if include_system { + tokio::join!(select.all(db), select_system.all(db)) + } else { + (select.all(db).await, Ok(Vec::new())) + }; + // Convert to Assignment type + let assignments: Vec = db_assignments + .map_err(|err| db_err(err, "fetching role assignments"))? + .into_iter() + .map(TryInto::::try_into) + .collect::, _>>()?; + + let system_assignments: Vec = db_system_assignments + .map_err(|err| db_err(err, "fetching system assignments"))? + .into_iter() + .map(TryInto::::try_into) + .collect::, _>>()?; + + // Merge and apply role implications + let mut result_map: BTreeMap = BTreeMap::new(); + + for assignment in assignments.into_iter().chain(system_assignments) { + result_map.insert(assignment.role_id.clone(), assignment.clone()); + if let Some(implies) = imply_rules.get(&assignment.role_id) { - let mut implied_assignment = assignment.clone(); - for implied in implies.iter() { - implied_assignment.role_id = implied.clone(); - db_assignments.insert(implied.clone(), implied_assignment.clone()); + for implied_role_id in implies.iter() { + let mut implied_assignment = assignment.clone(); + implied_assignment.role_id = implied_role_id.clone(); + result_map.insert(implied_role_id.clone(), implied_assignment); } } } - if !db_assignments.is_empty() { - // Get roles for the found IDs + // Fetch and update role names + if !result_map.is_empty() { let roles: HashMap = HashMap::from_iter( DbRole::find() .select_only() .columns([db_role::Column::Id, db_role::Column::Name]) - .filter(Expr::col(db_role::Column::Id).is_in(db_assignments.keys())) + .filter(Expr::col(db_role::Column::Id).is_in(result_map.keys())) .into_tuple() .all(db) .await .map_err(|err| db_err(err, "fetching roles by ids"))?, ); - let results: Result, _> = db_assignments - .values() - .map(|item| TryInto::::try_into((item, roles.get(&item.role_id)))) - .collect(); - results - } else { - Ok(Vec::new()) + + for assignment in result_map.values_mut() { + if let Some(name) = roles.get(&assignment.role_id) { + assignment.role_name = Some(name.clone()); + } + } } + + Ok(result_map.into_values().collect()) } #[cfg(test)] @@ -571,6 +602,7 @@ mod tests { let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([get_implied_rules_mock()]) .append_query_results([vec![get_role_assignment_mock("1")]]) + .append_query_results([vec![get_role_system_assignment_mock("2")]]) .append_query_results([vec![ get_role_mock("1", "rname"), get_role_mock("2", "rname2"), @@ -606,6 +638,11 @@ mod tests { r#"SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment""#, [] ), + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT "system_assignment"."type", "system_assignment"."actor_id", "system_assignment"."target_id", "system_assignment"."role_id", "system_assignment"."inherited" FROM "system_assignment" WHERE "system_assignment"."target_id" = $1"#, + ["system".into()] + ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"SELECT "role"."id", "role"."name" FROM "role" WHERE "id" IN ($1, $2)"#, diff --git a/src/assignment/mod.rs b/src/assignment/mod.rs index 9d9b4934..9ebc7ca6 100644 --- a/src/assignment/mod.rs +++ b/src/assignment/mod.rs @@ -175,6 +175,12 @@ impl AssignmentApi for AssignmentProvider { r#type: RoleAssignmentTargetType::Domain, inherited: Some(false), }); + } else if let Some(val) = ¶ms.system { + targets.push(RoleAssignmentTarget { + id: val.clone(), + r#type: RoleAssignmentTargetType::System, + inherited: Some(false), + }) } request.targets(targets); request.actors(actors);