diff --git a/mp:w b/mp:w new file mode 100644 index 00000000..23514306 --- /dev/null +++ b/mp:w @@ -0,0 +1,62 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +pub mod assignment; +pub mod role; + +use async_trait::async_trait; + +use crate::assignment::AssignmentProviderError; +use crate::keystone::ServiceState; + +pub use crate::assignment::types::assignment::{ + Assignment, AssignmentBuilder, AssignmentBuilderError, AssignmentType, + RoleAssignmentListForMultipleActorTargetParameters, +, + RoleAssignmentListForMultipleActorTargetParametersBuilder, RoleAssignmentListParameters, + RoleAssignmentListParametersBuilder, RoleAssignmentListParametersBuilderError, + RoleAssignmentTarget, +}; +pub use crate::assignment::types::role::{Role, RoleBuilder, RoleBuilderError, RoleListParameters}; + +#[async_trait] +pub trait AssignmentApi: Send + Sync + Clone { + /// List Roles. + async fn list_roles( + &self, + state: &ServiceState, + params: &RoleListParameters, + ) -> Result, AssignmentProviderError>; + + /// Get a single role. + async fn get_role<'a>( + &self, + state: &ServiceState, + role_id: &'a str, + ) -> Result, AssignmentProviderError>; + + /// List role assignments for given target/role/actor. + async fn list_role_assignments( + &self, + state: &ServiceState, + params: &RoleAssignmentListParameters, + ) -> Result, AssignmentProviderError>; + + /// Create assignment grant. + async fn create_grant( + &self, + state: &ServiceState, + params: &Assignment, + ) -> Result<(), AssignmentProviderError>; +} diff --git a/src/assignment/backend.rs b/src/assignment/backend.rs new file mode 100644 index 00000000..7831ef40 --- /dev/null +++ b/src/assignment/backend.rs @@ -0,0 +1,88 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +pub mod error; +pub mod sql; + +use async_trait::async_trait; +use dyn_clone::DynClone; + +use crate::assignment::AssignmentProviderError; +use crate::config::Config; +use crate::keystone::ServiceState; + +pub use crate::assignment::types::assignment::{ + Assignment, AssignmentBuilder, AssignmentBuilderError, AssignmentType, + RoleAssignmentListForMultipleActorTargetParameters, + RoleAssignmentListForMultipleActorTargetParametersBuilder, RoleAssignmentListParameters, + RoleAssignmentListParametersBuilder, RoleAssignmentListParametersBuilderError, + RoleAssignmentTarget, +}; +pub use crate::assignment::types::role::{Role, RoleBuilder, RoleBuilderError, RoleListParameters}; + +pub use sql::SqlBackend; + +#[async_trait] +pub trait AssignmentBackend: DynClone + Send + Sync + std::fmt::Debug { + /// Set config + fn set_config(&mut self, config: Config); + + /// List Roles + async fn list_roles( + &self, + state: &ServiceState, + params: &RoleListParameters, + ) -> Result, AssignmentProviderError>; + + /// Get single role by ID + async fn get_role<'a>( + &self, + state: &ServiceState, + id: &'a str, + ) -> Result, AssignmentProviderError>; + + /// List Role assignments + async fn list_assignments( + &self, + state: &ServiceState, + params: &RoleAssignmentListParameters, + ) -> Result, AssignmentProviderError>; + + /// List all role assignments for multiple actors on multiple 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) + async fn list_assignments_for_multiple_actors_and_targets( + &self, + state: &ServiceState, + params: &RoleAssignmentListForMultipleActorTargetParameters, + ) -> Result, AssignmentProviderError>; + + /// Create assignment grant. + async fn create_grant( + &self, + state: &ServiceState, + params: Assignment, + ) -> Result; + + /// Check assignment grant. + async fn check_grant( + &self, + state: &ServiceState, + params: &Assignment, + ) -> Result; +} + +dyn_clone::clone_trait_object!(AssignmentBackend); diff --git a/src/assignment/backends/error.rs b/src/assignment/backend/error.rs similarity index 98% rename from src/assignment/backends/error.rs rename to src/assignment/backend/error.rs index ac6150ef..027537b3 100644 --- a/src/assignment/backends/error.rs +++ b/src/assignment/backend/error.rs @@ -40,6 +40,10 @@ pub enum AssignmentDatabaseError { source: RoleBuilderError, }, + /// Invalid assignment type. + #[error("{0}")] + InvalidAssignmentType(String), + /// Conflict #[error("{message}")] Conflict { message: String, context: String }, @@ -54,9 +58,6 @@ pub enum AssignmentDatabaseError { source: sea_orm::DbErr, context: String, }, - - #[error("{0}")] - InvalidAssignmentType(String), } /// Convert the DB error into the [AssignmentDatabaseError] with the context information. diff --git a/src/assignment/backends/sql.rs b/src/assignment/backend/sql.rs similarity index 78% rename from src/assignment/backends/sql.rs rename to src/assignment/backend/sql.rs index 23c9bb66..d15c9f6a 100644 --- a/src/assignment/backends/sql.rs +++ b/src/assignment/backend/sql.rs @@ -15,7 +15,7 @@ use async_trait::async_trait; use super::super::types::*; -use crate::assignment::AssignmentProviderError; +use crate::assignment::{AssignmentProviderError, backend::AssignmentBackend}; use crate::config::Config; use crate::keystone::ServiceState; @@ -79,4 +79,24 @@ impl AssignmentBackend for SqlBackend { .await?, ) } + + /// Create assignment grant. + #[tracing::instrument(level = "info", skip(self, state))] + async fn create_grant( + &self, + state: &ServiceState, + grant: Assignment, + ) -> Result { + Ok(assignment::create(&state.db, grant).await?) + } + + /// Check assignment grant. + #[tracing::instrument(level = "info", skip(self, state))] + async fn check_grant( + &self, + state: &ServiceState, + grant: &Assignment, + ) -> Result { + Ok(assignment::check(&state.db, grant).await?) + } } diff --git a/src/assignment/backend/sql/assignment.rs b/src/assignment/backend/sql/assignment.rs new file mode 100644 index 00000000..00bca168 --- /dev/null +++ b/src/assignment/backend/sql/assignment.rs @@ -0,0 +1,276 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 +//! Role assignment database backend. + +//use crate::assignment::AssignmentProviderError; +use crate::assignment::backend::error::AssignmentDatabaseError; +use crate::assignment::types::*; +use crate::db::entity::{ + assignment as db_assignment, role as db_role, sea_orm_active_enums::Type as DbAssignmentType, + system_assignment as db_system_assignment, +}; + +mod check; +mod create; +mod list; + +pub use check::check; +pub use create::create; +pub use list::list; +pub use list::list_for_multiple_actors_and_targets; + +impl TryFrom for Assignment { + type Error = AssignmentDatabaseError; + + fn try_from(value: db_assignment::Model) -> Result { + let mut builder = AssignmentBuilder::default(); + builder.role_id(value.role_id.clone()); + builder.actor_id(value.actor_id.clone()); + builder.target_id(value.target_id.clone()); + builder.inherited(value.inherited); + builder.r#type(AssignmentType::try_from(value.r#type)?); + + Ok(builder.build()?) + } +} + +impl TryFrom for Assignment { + type Error = AssignmentDatabaseError; + + fn try_from(value: db_system_assignment::Model) -> Result { + let mut builder = AssignmentBuilder::default(); + builder.role_id(value.role_id.clone()); + builder.actor_id(value.actor_id.clone()); + builder.target_id(value.target_id.clone()); + builder.inherited(value.inherited); + builder.r#type(AssignmentType::try_from(value.r#type.as_ref())?); + + Ok(builder.build()?) + } +} + +impl TryFrom<&db_assignment::Model> for Assignment { + type Error = AssignmentDatabaseError; + + fn try_from(value: &db_assignment::Model) -> Result { + let mut builder = AssignmentBuilder::default(); + builder.role_id(value.role_id.clone()); + builder.actor_id(value.actor_id.clone()); + builder.target_id(value.target_id.clone()); + builder.inherited(value.inherited); + builder.r#type(AssignmentType::try_from(value.r#type.clone())?); + + Ok(builder.build()?) + } +} + +impl TryFrom<(&db_assignment::Model, Option<&String>)> for Assignment { + type Error = AssignmentDatabaseError; + + fn try_from(value: (&db_assignment::Model, Option<&String>)) -> Result { + let mut builder = AssignmentBuilder::default(); + builder.role_id(value.0.role_id.clone()); + builder.actor_id(value.0.actor_id.clone()); + builder.target_id(value.0.target_id.clone()); + builder.inherited(value.0.inherited); + builder.r#type(AssignmentType::try_from(value.0.r#type.clone())?); + if let Some(val) = value.1 { + builder.role_name(val.clone()); + } + + Ok(builder.build()?) + } +} + +impl TryFrom<(db_assignment::Model, Option)> for Assignment { + type Error = AssignmentDatabaseError; + + fn try_from( + value: (db_assignment::Model, Option), + ) -> Result { + let mut builder = AssignmentBuilder::default(); + builder.role_id(value.0.role_id.clone()); + builder.actor_id(value.0.actor_id.clone()); + builder.target_id(value.0.target_id.clone()); + builder.inherited(value.0.inherited); + builder.r#type(AssignmentType::try_from(value.0.r#type)?); + if let Some(val) = &value.1 { + builder.role_name(val.name.clone()); + } + + Ok(builder.build()?) + } +} + +impl TryFrom<(db_system_assignment::Model, Option)> for Assignment { + type Error = AssignmentDatabaseError; + + fn try_from( + value: (db_system_assignment::Model, Option), + ) -> Result { + let mut builder = AssignmentBuilder::default(); + builder.role_id(value.0.role_id.clone()); + builder.actor_id(value.0.actor_id.clone()); + builder.target_id(value.0.target_id.clone()); + builder.inherited(value.0.inherited); + builder.r#type(AssignmentType::try_from(value.0.r#type.as_ref())?); + if let Some(val) = &value.1 { + builder.role_name(val.name.clone()); + } + + Ok(builder.build()?) + } +} + +impl TryFrom for AssignmentType { + type Error = AssignmentDatabaseError; + fn try_from(value: DbAssignmentType) -> Result { + match value { + DbAssignmentType::GroupDomain => Ok(Self::GroupDomain), + DbAssignmentType::GroupProject => Ok(Self::GroupProject), + DbAssignmentType::UserDomain => Ok(Self::UserDomain), + DbAssignmentType::UserProject => Ok(Self::UserProject), + } + } +} + +impl TryFrom<&AssignmentType> for DbAssignmentType { + type Error = AssignmentDatabaseError; + fn try_from(value: &AssignmentType) -> Result { + match value { + AssignmentType::GroupDomain => Ok(Self::GroupDomain), + AssignmentType::GroupProject => Ok(Self::GroupProject), + AssignmentType::UserDomain => Ok(Self::UserDomain), + AssignmentType::UserProject => Ok(Self::UserProject), + AssignmentType::UserSystem => { + Err(Self::Error::InvalidAssignmentType("UserSystem".into())) + } + AssignmentType::GroupSystem => { + Err(Self::Error::InvalidAssignmentType("GroupSystem".into())) + } + } + } +} + +impl TryFrom<&str> for AssignmentType { + type Error = AssignmentDatabaseError; + fn try_from(value: &str) -> Result { + match value { + "GroupDomain" => Ok(Self::GroupDomain), + "GroupProject" => Ok(Self::GroupProject), + "GroupSystem" => Ok(Self::GroupSystem), + "UserDomain" => Ok(Self::UserDomain), + "UserProject" => Ok(Self::UserProject), + "UserSystem" => Ok(Self::UserSystem), + _ => Err(AssignmentDatabaseError::InvalidAssignmentType(value.into())), + } + } +} + +#[cfg(test)] +mod tests { + use sea_orm::Value; + use std::collections::BTreeMap; + + use crate::db::entity::{ + assignment, implied_role, role, sea_orm_active_enums, system_assignment, + }; + + pub(super) fn get_role_assignment_mock>(role_id: S) -> assignment::Model { + assignment::Model { + role_id: role_id.as_ref().to_string(), + actor_id: "actor".into(), + target_id: "target".into(), + r#type: sea_orm_active_enums::Type::UserProject, + inherited: false, + } + } + + pub(super) fn get_role_system_assignment_mock>( + role_id: S, + ) -> system_assignment::Model { + system_assignment::Model { + role_id: role_id.as_ref().to_string(), + actor_id: "actor".into(), + target_id: "system".into(), + r#type: "UserSystem".into(), + inherited: false, + } + } + + pub(super) fn get_role_mock, SN: AsRef>( + role_id: SI, + role_name: SN, + ) -> BTreeMap { + BTreeMap::from([ + ( + "id".to_string(), + Value::String(Some(Box::new(role_id.as_ref().to_string()))), + ), + ( + "name".to_string(), + Value::String(Some(Box::new(role_name.as_ref().to_string()))), + ), + ]) + } + + pub(super) fn get_implied_rules_mock() -> Vec { + vec![implied_role::Model { + prior_role_id: "1".to_string(), + implied_role_id: "2".to_string(), + }] + } + + pub(super) fn get_role_assignment_with_role_mock>( + role_id: S, + ) -> (assignment::Model, role::Model) { + ( + assignment::Model { + role_id: role_id.as_ref().to_string(), + actor_id: "actor".into(), + target_id: "target".into(), + r#type: sea_orm_active_enums::Type::UserProject, + inherited: false, + }, + role::Model { + id: role_id.as_ref().to_string(), + name: role_id.as_ref().to_string(), + extra: None, + domain_id: String::new(), + description: None, + }, + ) + } + + pub(super) fn get_role_system_assignment_with_role_mock>( + role_id: S, + ) -> (system_assignment::Model, role::Model) { + ( + system_assignment::Model { + role_id: role_id.as_ref().to_string(), + actor_id: "actor".into(), + target_id: "system".into(), + r#type: "UserSystem".into(), + inherited: false, + }, + role::Model { + id: role_id.as_ref().to_string(), + name: role_id.as_ref().to_string(), + extra: None, + domain_id: String::new(), + description: None, + }, + ) + } +} diff --git a/src/assignment/backend/sql/assignment/check.rs b/src/assignment/backend/sql/assignment/check.rs new file mode 100644 index 00000000..c324df8f --- /dev/null +++ b/src/assignment/backend/sql/assignment/check.rs @@ -0,0 +1,215 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 +//! Check grant presence. + +use sea_orm::DatabaseConnection; +use sea_orm::entity::*; +use sea_orm::query::*; + +use crate::assignment::backend::error::{AssignmentDatabaseError, db_err}; +use crate::assignment::types::*; +use crate::db::entity::{ + assignment as db_assignment, + prelude::{Assignment as DbAssignment, SystemAssignment as DbSystemAssignment}, + sea_orm_active_enums::Type as DbAssignmentType, + system_assignment as db_system_assignment, +}; + +/// Check whether the grant exists. +/// +/// # Result +/// +/// * `Ok(true)` when the grant is present. +/// * `Ok(false)` when the grant does not exist +pub async fn check( + db: &DatabaseConnection, + grant: &Assignment, +) -> Result { + let count: u64 = match &grant.r#type { + t @ AssignmentType::GroupDomain + | t @ AssignmentType::GroupProject + | t @ AssignmentType::UserDomain + | t @ AssignmentType::UserProject => DbAssignment::find() + .filter(db_assignment::Column::RoleId.eq(grant.role_id.as_str())) + .filter(db_assignment::Column::TargetId.eq(grant.target_id.as_str())) + .filter(db_assignment::Column::ActorId.eq(grant.actor_id.as_str())) + .filter(db_assignment::Column::Type.eq(DbAssignmentType::try_from(t)?)) + .filter(db_assignment::Column::Inherited.eq(grant.inherited)) + .count(db) + .await + .map_err(|err| db_err(err, "checking grant"))?, + t @ AssignmentType::GroupSystem | t @ AssignmentType::UserSystem => { + DbSystemAssignment::find() + .filter(db_system_assignment::Column::RoleId.eq(grant.role_id.as_str())) + .filter(db_system_assignment::Column::TargetId.eq(grant.target_id.as_str())) + .filter(db_system_assignment::Column::ActorId.eq(grant.actor_id.as_str())) + .filter(db_system_assignment::Column::Type.eq(t.to_string())) + .filter(db_system_assignment::Column::Inherited.eq(grant.inherited)) + .count(db) + .await + .map_err(|err| db_err(err, "checking system grant"))? + } + }; + Ok(count > 0) +} + +#[cfg(test)] +mod tests { + use sea_orm::{DatabaseBackend, IntoMockRow, MockDatabase, Transaction}; + use std::collections::BTreeMap; + + use super::*; + + #[tokio::test] + async fn test_check() { + // Create MockDatabase with mock query results + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([vec![ + BTreeMap::from([("num_items", Into::::into(1i64))]).into_mock_row(), + ]]) + .append_query_results([vec![ + BTreeMap::from([("num_items", Into::::into(0i64))]).into_mock_row(), + ]]) + .into_connection(); + assert!( + check( + &db, + &Assignment { + role_id: "role_id".into(), + role_name: None, + actor_id: "actor_id".into(), + target_id: "target_id".into(), + r#type: AssignmentType::UserProject, + inherited: true + } + ) + .await + .unwrap(), + ); + assert!( + !check( + &db, + &Assignment { + role_id: "role_id2".into(), + role_name: None, + actor_id: "actor_id".into(), + target_id: "target_id".into(), + r#type: AssignmentType::UserProject, + inherited: true + } + ) + .await + .unwrap(), + ); + // Checking transaction log + assert_eq!( + db.into_transaction_log(), + [ + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT COUNT(*) AS num_items FROM (SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment" WHERE "assignment"."role_id" = $1 AND "assignment"."target_id" = $2 AND "assignment"."actor_id" = $3 AND "assignment"."type" = (CAST($4 AS "type")) AND "assignment"."inherited" = $5) AS "sub_query""#, + [ + "role_id".into(), + "target_id".into(), + "actor_id".into(), + "UserProject".into(), + true.into(), + ] + ), + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT COUNT(*) AS num_items FROM (SELECT CAST("assignment"."type" AS "text"), "assignment"."actor_id", "assignment"."target_id", "assignment"."role_id", "assignment"."inherited" FROM "assignment" WHERE "assignment"."role_id" = $1 AND "assignment"."target_id" = $2 AND "assignment"."actor_id" = $3 AND "assignment"."type" = (CAST($4 AS "type")) AND "assignment"."inherited" = $5) AS "sub_query""#, + [ + "role_id2".into(), + "target_id".into(), + "actor_id".into(), + "UserProject".into(), + true.into(), + ] + ), + ] + ); + } + + #[tokio::test] + async fn test_check_system() { + // Create MockDatabase with mock query results + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([vec![ + BTreeMap::from([("num_items", Into::::into(1i64))]).into_mock_row(), + ]]) + .append_query_results([vec![ + BTreeMap::from([("num_items", Into::::into(0i64))]).into_mock_row(), + ]]) + .into_connection(); + assert!( + check( + &db, + &Assignment { + role_id: "role_id".into(), + role_name: None, + actor_id: "actor_id".into(), + target_id: "target_id".into(), + r#type: AssignmentType::UserSystem, + inherited: true + } + ) + .await + .unwrap(), + ); + assert!( + !check( + &db, + &Assignment { + role_id: "role_id2".into(), + role_name: None, + actor_id: "actor_id".into(), + target_id: "target_id".into(), + r#type: AssignmentType::UserSystem, + inherited: true + } + ) + .await + .unwrap(), + ); + // Checking transaction log + assert_eq!( + db.into_transaction_log(), + [ + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT COUNT(*) AS num_items FROM (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"."actor_id" = $3 AND "system_assignment"."type" = $4 AND "system_assignment"."inherited" = $5) AS "sub_query""#, + [ + "role_id".into(), + "target_id".into(), + "actor_id".into(), + "UserSystem".into(), + true.into(), + ] + ), + Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"SELECT COUNT(*) AS num_items FROM (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"."actor_id" = $3 AND "system_assignment"."type" = $4 AND "system_assignment"."inherited" = $5) AS "sub_query""#, + [ + "role_id2".into(), + "target_id".into(), + "actor_id".into(), + "UserSystem".into(), + true.into(), + ] + ), + ] + ); + } +} diff --git a/src/assignment/backend/sql/assignment/create.rs b/src/assignment/backend/sql/assignment/create.rs new file mode 100644 index 00000000..9e8274a5 --- /dev/null +++ b/src/assignment/backend/sql/assignment/create.rs @@ -0,0 +1,185 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +use sea_orm::DatabaseConnection; +use sea_orm::entity::*; + +use crate::assignment::backend::error::{AssignmentDatabaseError, db_err}; +use crate::assignment::types::*; +use crate::db::entity::{ + assignment as db_assignment, sea_orm_active_enums::Type as DbAssignmentType, + system_assignment as db_system_assignment, +}; + +/// Create assignment grant. +pub async fn create( + db: &DatabaseConnection, + assignment: Assignment, +) -> Result { + match assignment.r#type { + AssignmentType::GroupDomain + | AssignmentType::GroupProject + | AssignmentType::UserDomain + | AssignmentType::UserProject => Assignment::try_from( + db_assignment::ActiveModel { + r#type: Set(DbAssignmentType::try_from(&assignment.r#type)?), + role_id: Set(assignment.role_id), + actor_id: Set(assignment.actor_id), + target_id: Set(assignment.target_id), + inherited: Set(assignment.inherited), + } + .insert(db) + .await + .map_err(|err| db_err(err, "persisting assignment"))?, + ), + other => Assignment::try_from( + db_system_assignment::ActiveModel { + r#type: Set(other.to_string()), + role_id: Set(assignment.role_id), + actor_id: Set(assignment.actor_id), + target_id: Set(assignment.target_id), + inherited: Set(assignment.inherited), + } + .insert(db) + .await + .map_err(|err| db_err(err, "persisting system assignment"))?, + ), + } +} + +#[cfg(test)] +mod tests { + use sea_orm::{DatabaseBackend, MockDatabase, Transaction}; + + use super::super::tests::*; + use super::*; + + #[tokio::test] + async fn test_create() { + // Create MockDatabase with mock query results + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([vec![get_role_assignment_mock("role_id")]]) + .into_connection(); + assert_eq!( + create( + &db, + Assignment { + role_id: "role_id".into(), + role_name: None, + actor_id: "actor_id".into(), + target_id: "target_id".into(), + r#type: AssignmentType::UserProject, + inherited: true + } + ) + .await + .unwrap(), + Assignment { + role_id: "role_id".into(), + role_name: None, + actor_id: "actor".into(), + target_id: "target".into(), + r#type: AssignmentType::UserProject, + inherited: false + } + ); + // Checking transaction log + assert_eq!( + db.into_transaction_log(), + [Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"INSERT INTO "assignment" ("type", "actor_id", "target_id", "role_id", "inherited") VALUES (CAST($1 AS "type"), $2, $3, $4, $5) RETURNING CAST("type" AS "text"), "actor_id", "target_id", "role_id", "inherited""#, + [ + "UserProject".into(), + "actor_id".into(), + "target_id".into(), + "role_id".into(), + true.into(), + ] + ),] + ); + } + + #[tokio::test] + async fn test_create_system_user() { + // Create MockDatabase with mock query results + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([vec![get_role_assignment_mock("role_id")]]) + .into_connection(); + create( + &db, + Assignment { + role_id: "role_id".into(), + role_name: None, + actor_id: "actor_id".into(), + target_id: "target_id".into(), + r#type: AssignmentType::UserSystem, + inherited: true, + }, + ) + .await + .unwrap(); + // Checking transaction log + assert_eq!( + db.into_transaction_log(), + [Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"INSERT INTO "system_assignment" ("type", "actor_id", "target_id", "role_id", "inherited") VALUES ($1, $2, $3, $4, $5) RETURNING "type", "actor_id", "target_id", "role_id", "inherited""#, + [ + "UserSystem".into(), + "actor_id".into(), + "target_id".into(), + "role_id".into(), + true.into(), + ] + ),] + ); + } + + #[tokio::test] + async fn test_create_system_group() { + // Create MockDatabase with mock query results + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([vec![get_role_assignment_mock("role_id")]]) + .into_connection(); + create( + &db, + Assignment { + role_id: "role_id".into(), + role_name: None, + actor_id: "actor_id".into(), + target_id: "target_id".into(), + r#type: AssignmentType::GroupSystem, + inherited: true, + }, + ) + .await + .unwrap(); + // Checking transaction log + assert_eq!( + db.into_transaction_log(), + [Transaction::from_sql_and_values( + DatabaseBackend::Postgres, + r#"INSERT INTO "system_assignment" ("type", "actor_id", "target_id", "role_id", "inherited") VALUES ($1, $2, $3, $4, $5) RETURNING "type", "actor_id", "target_id", "role_id", "inherited""#, + [ + "GroupSystem".into(), + "actor_id".into(), + "target_id".into(), + "role_id".into(), + true.into(), + ] + ),] + ); + } +} diff --git a/src/assignment/backends/sql/assignment.rs b/src/assignment/backend/sql/assignment/list.rs similarity index 78% rename from src/assignment/backends/sql/assignment.rs rename to src/assignment/backend/sql/assignment/list.rs index b972c462..87fb1676 100644 --- a/src/assignment/backends/sql/assignment.rs +++ b/src/assignment/backend/sql/assignment/list.rs @@ -18,7 +18,8 @@ use sea_orm::prelude::Expr; use sea_orm::query::*; use std::collections::{BTreeMap, HashMap}; -use crate::assignment::backends::error::{AssignmentDatabaseError, db_err}; +use crate::assignment::backend::error::{AssignmentDatabaseError, db_err}; +use crate::assignment::backend::sql::implied_role; use crate::assignment::types::*; use crate::config::Config; use crate::db::entity::{ @@ -159,7 +160,7 @@ pub async fn list_for_multiple_actors_and_targets( } // Get all implied rules - let imply_rules = super::implied_role::list_rules(db, true).await?; + let imply_rules = implied_role::list_rules(db, true).await?; let mut db_assignments: BTreeMap = BTreeMap::new(); // Get assignments resolving the roles inference @@ -201,223 +202,22 @@ pub async fn list_for_multiple_actors_and_targets( } } -impl TryFrom for Assignment { - type Error = AssignmentDatabaseError; - - fn try_from(value: db_assignment::Model) -> Result { - let mut builder = AssignmentBuilder::default(); - builder.role_id(value.role_id.clone()); - builder.actor_id(value.actor_id.clone()); - builder.target_id(value.target_id.clone()); - builder.inherited(value.inherited); - builder.r#type(AssignmentType::try_from(value.r#type)?); - - Ok(builder.build()?) - } -} - -impl TryFrom for Assignment { - type Error = AssignmentDatabaseError; - - fn try_from(value: db_system_assignment::Model) -> Result { - let mut builder = AssignmentBuilder::default(); - builder.role_id(value.role_id.clone()); - builder.actor_id(value.actor_id.clone()); - builder.target_id(value.target_id.clone()); - builder.inherited(value.inherited); - builder.r#type(AssignmentType::try_from(value.r#type.as_ref())?); - - Ok(builder.build()?) - } -} - -impl TryFrom<&db_assignment::Model> for Assignment { - type Error = AssignmentDatabaseError; - - fn try_from(value: &db_assignment::Model) -> Result { - let mut builder = AssignmentBuilder::default(); - builder.role_id(value.role_id.clone()); - builder.actor_id(value.actor_id.clone()); - builder.target_id(value.target_id.clone()); - builder.inherited(value.inherited); - builder.r#type(AssignmentType::try_from(value.r#type.clone())?); - - Ok(builder.build()?) - } -} - -impl TryFrom<(&db_assignment::Model, Option<&String>)> for Assignment { - type Error = AssignmentDatabaseError; - - fn try_from(value: (&db_assignment::Model, Option<&String>)) -> Result { - let mut builder = AssignmentBuilder::default(); - builder.role_id(value.0.role_id.clone()); - builder.actor_id(value.0.actor_id.clone()); - builder.target_id(value.0.target_id.clone()); - builder.inherited(value.0.inherited); - builder.r#type(AssignmentType::try_from(value.0.r#type.clone())?); - if let Some(val) = value.1 { - builder.role_name(val.clone()); - } - - Ok(builder.build()?) - } -} - -impl TryFrom<(db_assignment::Model, Option)> for Assignment { - type Error = AssignmentDatabaseError; - - fn try_from( - value: (db_assignment::Model, Option), - ) -> Result { - let mut builder = AssignmentBuilder::default(); - builder.role_id(value.0.role_id.clone()); - builder.actor_id(value.0.actor_id.clone()); - builder.target_id(value.0.target_id.clone()); - builder.inherited(value.0.inherited); - builder.r#type(AssignmentType::try_from(value.0.r#type)?); - if let Some(val) = &value.1 { - builder.role_name(val.name.clone()); - } - - Ok(builder.build()?) - } -} - -impl TryFrom<(db_system_assignment::Model, Option)> for Assignment { - type Error = AssignmentDatabaseError; - - fn try_from( - value: (db_system_assignment::Model, Option), - ) -> Result { - let mut builder = AssignmentBuilder::default(); - builder.role_id(value.0.role_id.clone()); - builder.actor_id(value.0.actor_id.clone()); - builder.target_id(value.0.target_id.clone()); - builder.inherited(value.0.inherited); - builder.r#type(AssignmentType::try_from(value.0.r#type.as_ref())?); - if let Some(val) = &value.1 { - builder.role_name(val.name.clone()); - } - - Ok(builder.build()?) - } -} - -impl TryFrom for AssignmentType { - type Error = AssignmentDatabaseError; - fn try_from(value: DbAssignmentType) -> Result { - match value { - DbAssignmentType::GroupDomain => Ok(Self::GroupDomain), - DbAssignmentType::GroupProject => Ok(Self::GroupProject), - DbAssignmentType::UserDomain => Ok(Self::UserDomain), - DbAssignmentType::UserProject => Ok(Self::UserProject), - } - } -} - -impl TryFrom<&str> for AssignmentType { - type Error = AssignmentDatabaseError; - fn try_from(value: &str) -> Result { - match value { - "UserSystem" => Ok(Self::UserSystem), - _ => Err(AssignmentDatabaseError::InvalidAssignmentType(value.into())), - } - } -} - #[cfg(test)] mod tests { use sea_orm::{DatabaseBackend, MockDatabase, Transaction}; - use std::collections::BTreeMap; use crate::config::Config; - use crate::db::entity::{ - assignment, implied_role, role, sea_orm_active_enums, system_assignment, - }; + use crate::db::entity::assignment; + use super::super::tests::*; use super::*; - fn get_role_assignment_mock(role_id: String) -> assignment::Model { - assignment::Model { - role_id: role_id.clone(), - actor_id: "actor".into(), - target_id: "target".into(), - r#type: sea_orm_active_enums::Type::UserProject, - inherited: false, - } - } - - fn get_role_system_assignment_mock(role_id: String) -> system_assignment::Model { - system_assignment::Model { - role_id: role_id.clone(), - actor_id: "actor".into(), - target_id: "system".into(), - r#type: "UserSystem".into(), - inherited: false, - } - } - - fn get_role_mock(role_id: String, role_name: String) -> BTreeMap { - BTreeMap::from([ - ("id".to_string(), Value::String(Some(Box::new(role_id)))), - ("name".to_string(), Value::String(Some(Box::new(role_name)))), - ]) - } - - fn get_implied_rules_mock() -> Vec { - vec![implied_role::Model { - prior_role_id: "1".to_string(), - implied_role_id: "2".to_string(), - }] - } - - fn get_role_assignment_with_role_mock(role_id: String) -> (assignment::Model, role::Model) { - ( - assignment::Model { - role_id: role_id.clone(), - actor_id: "actor".into(), - target_id: "target".into(), - r#type: sea_orm_active_enums::Type::UserProject, - inherited: false, - }, - role::Model { - id: role_id.clone(), - name: role_id.clone(), - extra: None, - domain_id: String::new(), - description: None, - }, - ) - } - - fn get_role_system_assignment_with_role_mock( - role_id: String, - ) -> (system_assignment::Model, role::Model) { - ( - system_assignment::Model { - role_id: role_id.clone(), - actor_id: "actor".into(), - target_id: "system".into(), - r#type: "UserSystem".into(), - inherited: false, - }, - role::Model { - id: role_id.clone(), - name: role_id.clone(), - extra: None, - domain_id: String::new(), - description: None, - }, - ) - } - #[tokio::test] async fn test_list_no_params() { // Create MockDatabase with mock query results let db = MockDatabase::new(DatabaseBackend::Postgres) - .append_query_results([vec![get_role_assignment_mock("1".into())]]) - .append_query_results([vec![get_role_system_assignment_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_mock("1")]]) + .append_query_results([vec![get_role_system_assignment_mock("1")]]) .into_connection(); let config = Config::default(); assert_eq!( @@ -465,8 +265,8 @@ mod tests { async fn test_list_role_id() { // Create MockDatabase with mock query results let db = MockDatabase::new(DatabaseBackend::Postgres) - .append_query_results([vec![get_role_assignment_mock("1".into())]]) - .append_query_results([vec![get_role_system_assignment_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_mock("1")]]) + .append_query_results([vec![get_role_system_assignment_mock("1")]]) .into_connection(); let config = Config::default(); assert_eq!( @@ -521,7 +321,7 @@ mod tests { async fn test_list_project_id() { // Create MockDatabase with mock query results let db = MockDatabase::new(DatabaseBackend::Postgres) - .append_query_results([vec![get_role_assignment_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_mock("1")]]) .into_connection(); let config = Config::default(); assert_eq!( @@ -559,8 +359,8 @@ mod tests { async fn test_list_include_names() { // Create MockDatabase with mock query results let db = MockDatabase::new(DatabaseBackend::Postgres) - .append_query_results([vec![get_role_assignment_with_role_mock("1".into())]]) - .append_query_results([vec![get_role_system_assignment_with_role_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_with_role_mock("1")]]) + .append_query_results([vec![get_role_system_assignment_with_role_mock("1")]]) .into_connection(); let config = Config::default(); assert_eq!( @@ -616,10 +416,10 @@ mod tests { // Create MockDatabase with mock query results let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([get_implied_rules_mock()]) - .append_query_results([vec![get_role_assignment_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_mock("1")]]) .append_query_results([vec![ - get_role_mock("1".into(), "rname".into()), - get_role_mock("2".into(), "rname2".into()), + get_role_mock("1", "rname"), + get_role_mock("2", "rname2"), ]]) .into_connection(); let config = Config::default(); @@ -692,10 +492,10 @@ mod tests { let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([get_implied_rules_mock()]) - .append_query_results([vec![get_role_assignment_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_mock("1")]]) .append_query_results([vec![ - get_role_mock("1".into(), "rname".into()), - get_role_mock("2".into(), "rname2".into()), + get_role_mock("1", "rname"), + get_role_mock("2", "rname2"), ]]) .into_connection(); let config = Config::default(); @@ -759,10 +559,10 @@ mod tests { let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([get_implied_rules_mock()]) - .append_query_results([vec![get_role_assignment_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_mock("1")]]) .append_query_results([vec![ - get_role_mock("1".into(), "rname".into()), - get_role_mock("2".into(), "rname2".into()), + get_role_mock("1", "rname"), + get_role_mock("2", "rname2"), ]]) .into_connection(); let config = Config::default(); @@ -810,10 +610,10 @@ mod tests { let db = MockDatabase::new(DatabaseBackend::Postgres) .append_query_results([get_implied_rules_mock()]) - .append_query_results([vec![get_role_assignment_mock("1".into())]]) + .append_query_results([vec![get_role_assignment_mock("1")]]) .append_query_results([vec![ - get_role_mock("1".into(), "rname".into()), - get_role_mock("2".into(), "rname2".into()), + get_role_mock("1", "rname"), + get_role_mock("2", "rname2"), ]]) .into_connection(); let config = Config::default(); diff --git a/src/assignment/backends/sql/implied_role.rs b/src/assignment/backend/sql/implied_role.rs similarity index 98% rename from src/assignment/backends/sql/implied_role.rs rename to src/assignment/backend/sql/implied_role.rs index 583ff7df..5c0e6fa8 100644 --- a/src/assignment/backends/sql/implied_role.rs +++ b/src/assignment/backend/sql/implied_role.rs @@ -16,7 +16,7 @@ use sea_orm::DatabaseConnection; use sea_orm::entity::*; use std::collections::{BTreeMap, BTreeSet}; -use crate::assignment::backends::error::{AssignmentDatabaseError, db_err}; +use crate::assignment::backend::error::{AssignmentDatabaseError, db_err}; use crate::db::entity::prelude::ImpliedRole as DbImpliedRole; /// Build a resolved tree of role inference diff --git a/src/assignment/backends/sql/role.rs b/src/assignment/backend/sql/role.rs similarity index 98% rename from src/assignment/backends/sql/role.rs rename to src/assignment/backend/sql/role.rs index 1e113a6f..958feff7 100644 --- a/src/assignment/backends/sql/role.rs +++ b/src/assignment/backend/sql/role.rs @@ -18,7 +18,7 @@ use sea_orm::query::*; use serde_json::Value; use tracing::error; -use crate::assignment::backends::error::{AssignmentDatabaseError, db_err}; +use crate::assignment::backend::error::{AssignmentDatabaseError, db_err}; use crate::assignment::types::*; use crate::config::Config; use crate::db::entity::{prelude::Role as DbRole, role as db_role}; diff --git a/src/assignment/backends.rs b/src/assignment/backends.rs deleted file mode 100644 index a4618644..00000000 --- a/src/assignment/backends.rs +++ /dev/null @@ -1,16 +0,0 @@ -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -pub mod error; -pub mod sql; diff --git a/src/assignment/error.rs b/src/assignment/error.rs index 06b47969..7f1ef4b9 100644 --- a/src/assignment/error.rs +++ b/src/assignment/error.rs @@ -14,7 +14,7 @@ use thiserror::Error; -use crate::assignment::backends::error::*; +use crate::assignment::backend::error::*; use crate::assignment::types::assignment::RoleAssignmentListForMultipleActorTargetParametersBuilderError; use crate::assignment::types::*; use crate::identity::error::IdentityProviderError; @@ -50,6 +50,10 @@ pub enum AssignmentProviderError { source: IdentityProviderError, }, + /// Invalid assignment type. + #[error("{0}")] + InvalidAssignmentType(String), + #[error("building role assignment query: {}", source)] RoleAssignmentParametersBuilder { #[from] @@ -75,6 +79,7 @@ impl From for AssignmentProviderError { AssignmentDatabaseError::Conflict { message, .. } => Self::Conflict(message), AssignmentDatabaseError::RoleNotFound(x) => Self::RoleNotFound(x), AssignmentDatabaseError::Serde { source } => Self::Serde { source }, + AssignmentDatabaseError::InvalidAssignmentType(x) => Self::InvalidAssignmentType(x), _ => Self::AssignmentDatabaseError { source }, } } diff --git a/src/assignment/mock.rs b/src/assignment/mock.rs new file mode 100644 index 00000000..057cd3e1 --- /dev/null +++ b/src/assignment/mock.rs @@ -0,0 +1,61 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use mockall::mock; + +use crate::assignment::AssignmentApi; +use crate::assignment::AssignmentProviderError; +use crate::assignment::types::{ + Assignment, Role, RoleAssignmentListParameters, RoleListParameters, +}; +use crate::config::Config; +use crate::keystone::ServiceState; +use crate::plugin_manager::PluginManager; + +mock! { + pub AssignmentProvider { + pub fn new(cfg: &Config, plugin_manager: &PluginManager) -> Result; + } + + #[async_trait] + impl AssignmentApi for AssignmentProvider { + async fn list_roles( + &self, + state: &ServiceState, + params: &RoleListParameters, + ) -> Result, AssignmentProviderError>; + + async fn get_role<'a>( + &self, + state: &ServiceState, + id: &'a str, + ) -> Result, AssignmentProviderError>; + + async fn list_role_assignments( + &self, + state: &ServiceState, + params: &RoleAssignmentListParameters, + ) -> Result, AssignmentProviderError>; + + async fn create_grant( + &self, + state: &ServiceState, + params: Assignment, + ) -> Result; + } + + impl Clone for AssignmentProvider { + fn clone(&self) -> Self; + } +} diff --git a/src/assignment/mod.rs b/src/assignment/mod.rs index bf4d4456..86167c27 100644 --- a/src/assignment/mod.rs +++ b/src/assignment/mod.rs @@ -12,17 +12,17 @@ // SPDX-License-Identifier: Apache-2.0 use async_trait::async_trait; -#[cfg(test)] -use mockall::mock; -pub mod backends; +pub mod backend; pub mod error; -pub(crate) mod types; +#[cfg(test)] +mod mock; +pub mod types; -use crate::assignment::backends::sql::SqlBackend; +use crate::assignment::backend::{AssignmentBackend, SqlBackend}; use crate::assignment::error::AssignmentProviderError; use crate::assignment::types::{ - Assignment, AssignmentBackend, Role, RoleAssignmentListForMultipleActorTargetParametersBuilder, + Assignment, Role, RoleAssignmentListForMultipleActorTargetParametersBuilder, RoleAssignmentListParameters, RoleAssignmentTarget, RoleListParameters, }; use crate::config::Config; @@ -30,67 +30,15 @@ use crate::identity::IdentityApi; use crate::keystone::ServiceState; use crate::plugin_manager::PluginManager; +#[cfg(test)] +pub use mock::MockAssignmentProvider; +pub use types::AssignmentApi; + #[derive(Clone, Debug)] pub struct AssignmentProvider { backend_driver: Box, } -#[async_trait] -pub trait AssignmentApi: Send + Sync + Clone { - /// List Roles - async fn list_roles( - &self, - state: &ServiceState, - params: &RoleListParameters, - ) -> Result, AssignmentProviderError>; - - /// Get a single role - async fn get_role<'a>( - &self, - state: &ServiceState, - role_id: &'a str, - ) -> Result, AssignmentProviderError>; - - /// List role assignments for given target/role/actor - async fn list_role_assignments( - &self, - state: &ServiceState, - params: &RoleAssignmentListParameters, - ) -> Result, AssignmentProviderError>; -} - -#[cfg(test)] -mock! { - pub AssignmentProvider { - pub fn new(cfg: &Config, plugin_manager: &PluginManager) -> Result; - } - - #[async_trait] - impl AssignmentApi for AssignmentProvider { - async fn list_roles( - &self, - state: &ServiceState, - params: &RoleListParameters, - ) -> Result, AssignmentProviderError>; - - async fn get_role<'a>( - &self, - state: &ServiceState, - id: &'a str, - ) -> Result, AssignmentProviderError>; - - async fn list_role_assignments( - &self, - state: &ServiceState, - params: &RoleAssignmentListParameters, - ) -> Result, AssignmentProviderError>; - } - - impl Clone for AssignmentProvider { - fn clone(&self) -> Self; - } -} - impl AssignmentProvider { pub fn new( config: &Config, @@ -179,4 +127,14 @@ impl AssignmentApi for AssignmentProvider { self.backend_driver.list_assignments(state, params).await } } + + /// Create assignment grant. + #[tracing::instrument(level = "info", skip(self, state))] + async fn create_grant( + &self, + state: &ServiceState, + params: Assignment, + ) -> Result { + self.backend_driver.create_grant(state, params).await + } } diff --git a/src/assignment/types.rs b/src/assignment/types.rs index 1ba3fb5b..056a161c 100644 --- a/src/assignment/types.rs +++ b/src/assignment/types.rs @@ -16,10 +16,8 @@ pub mod assignment; pub mod role; use async_trait::async_trait; -use dyn_clone::DynClone; use crate::assignment::AssignmentProviderError; -use crate::config::Config; use crate::keystone::ServiceState; pub use crate::assignment::types::assignment::{ @@ -32,41 +30,32 @@ pub use crate::assignment::types::assignment::{ pub use crate::assignment::types::role::{Role, RoleBuilder, RoleBuilderError, RoleListParameters}; #[async_trait] -pub trait AssignmentBackend: DynClone + Send + Sync + std::fmt::Debug { - /// Set config - fn set_config(&mut self, config: Config); - - /// List Roles +pub trait AssignmentApi: Send + Sync + Clone { + /// List Roles. async fn list_roles( &self, state: &ServiceState, params: &RoleListParameters, - ) -> Result, AssignmentProviderError>; + ) -> Result, AssignmentProviderError>; - /// Get single role by ID + /// Get a single role. async fn get_role<'a>( &self, state: &ServiceState, - id: &'a str, + role_id: &'a str, ) -> Result, AssignmentProviderError>; - /// List Role assignments - async fn list_assignments( + /// List role assignments for given target/role/actor. + async fn list_role_assignments( &self, state: &ServiceState, params: &RoleAssignmentListParameters, - ) -> Result, AssignmentProviderError>; + ) -> Result, AssignmentProviderError>; - /// List all role assignments for multiple actors on multiple 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) - async fn list_assignments_for_multiple_actors_and_targets( + /// Create assignment grant. + async fn create_grant( &self, state: &ServiceState, - params: &RoleAssignmentListForMultipleActorTargetParameters, - ) -> Result, AssignmentProviderError>; + params: Assignment, + ) -> Result; } - -dyn_clone::clone_trait_object!(AssignmentBackend); diff --git a/src/assignment/types/assignment.rs b/src/assignment/types/assignment.rs index b43278ac..4c877c5f 100644 --- a/src/assignment/types/assignment.rs +++ b/src/assignment/types/assignment.rs @@ -14,6 +14,7 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; +use std::fmt; /// Role #[derive(Builder, Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -45,7 +46,20 @@ pub enum AssignmentType { GroupSystem, } -/// Parameters for listing role assignments for role/target/actor +impl fmt::Display for AssignmentType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::GroupDomain => write!(f, "GroupDomain"), + Self::GroupProject => write!(f, "GroupProject"), + Self::GroupSystem => write!(f, "GroupSystem"), + Self::UserDomain => write!(f, "UserDomain"), + Self::UserProject => write!(f, "UserProject"), + Self::UserSystem => write!(f, "UserSystem"), + } + } +} + +/// Parameters for listing role assignments for role/target/actor. #[derive(Builder, Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[builder(setter(strip_option, into))] pub struct RoleAssignmentListParameters { @@ -79,31 +93,30 @@ pub struct RoleAssignmentListParameters { /// If set to true, then the names of any entities returned will be include as well as their /// IDs. Any value other than 0 (including no value) will be interpreted as true. - /// - /// New in version 3.6 #[builder(default)] pub include_names: Option, } /// Querying effective role assignments for list of actors (typically user with all groups user is -/// member of) on list of targets (exactl project + inherited from uppoer projects/domain) +/// member of) on list of targets (exactl project + inherited from uppoer projects/domain). #[derive(Builder, Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[builder(setter(strip_option, into))] pub struct RoleAssignmentListForMultipleActorTargetParameters { - /// List of actors for which assignments are looked up + /// List of actors for which assignments are looked up. #[builder(default)] pub actors: Vec, - /// Optionally filter for the concrete role ID + /// Optionally filter for the concrete role ID. #[builder(default)] pub role_id: Option, - /// List of targets for which assignments are looked up + /// List of targets for which assignments are looked up. #[builder(default)] pub targets: Vec, } -/// Role assignment target which is either target_id or target_id with explicit inherited parameter +/// Role assignment target which is either target_id or target_id with explicit inherited +/// parameter. #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct RoleAssignmentTarget { pub target_id: String, diff --git a/src/plugin_manager.rs b/src/plugin_manager.rs index 4a5d3b5e..e644ed47 100644 --- a/src/plugin_manager.rs +++ b/src/plugin_manager.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; -use crate::assignment::types::AssignmentBackend; +use crate::assignment::backend::AssignmentBackend; use crate::catalog::backends::CatalogBackend; use crate::federation::types::FederationBackend; use crate::identity::types::IdentityBackend;