diff --git a/project-words.txt b/project-words.txt index c6dbbf95..01908fce 100644 --- a/project-words.txt +++ b/project-words.txt @@ -9,6 +9,7 @@ binascii btih buildx camino +Casbin chrono clippy codecov @@ -51,6 +52,7 @@ luckythelab mailcatcher mandelbrotset metainfo +Mgmt migth nanos NCCA diff --git a/src/app.rs b/src/app.rs index ab9a2066..bba7dac2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,6 +11,7 @@ use crate::config::validator::Validator; use crate::config::Configuration; use crate::databases::database; use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service}; +use crate::services::authorization::{CasbinConfiguration, CasbinEnforcer}; use crate::services::category::{self, DbCategoryRepository}; use crate::services::tag::{self, DbTagRepository}; use crate::services::torrent::{ @@ -62,6 +63,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running // From [net] config let config_bind_address = settings.net.bind_address; let opt_net_tsl = settings.net.tsl.clone(); + // Unstable config + let unstable = settings.unstable.clone(); // IMPORTANT: drop settings before starting server to avoid read locks that // leads to requests hanging. @@ -87,7 +90,19 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let torrent_tag_repository = Arc::new(DbTorrentTagRepository::new(database.clone())); let torrent_listing_generator = Arc::new(DbTorrentListingGenerator::new(database.clone())); let banned_user_list = Arc::new(DbBannedUserList::new(database.clone())); - let casbin_enforcer = Arc::new(authorization::CasbinEnforcer::new().await); + let casbin_enforcer = Arc::new( + if let Some(casbin) = unstable + .as_ref() + .and_then(|u| u.auth.as_ref()) + .and_then(|auth| auth.casbin.as_ref()) + { + println!("loading custom"); + CasbinEnforcer::with_configuration(CasbinConfiguration::new(&casbin.model, &casbin.policy)).await + } else { + println!("loading default"); + CasbinEnforcer::with_default_configuration().await + }, + ); // Services let authorization_service = Arc::new(authorization::Service::new(user_repository.clone(), casbin_enforcer.clone())); diff --git a/src/config/v2/mod.rs b/src/config/v2/mod.rs index e5519f56..cf8d185c 100644 --- a/src/config/v2/mod.rs +++ b/src/config/v2/mod.rs @@ -8,11 +8,13 @@ pub mod net; pub mod registration; pub mod tracker; pub mod tracker_statistics_importer; +pub mod unstable; pub mod website; use logging::Logging; use registration::Registration; use serde::{Deserialize, Serialize}; +use unstable::Unstable; use self::api::Api; use self::auth::{Auth, ClaimTokenPepper}; @@ -76,6 +78,10 @@ pub struct Settings { /// The tracker statistics importer job configuration. #[serde(default = "Settings::default_tracker_statistics_importer")] pub tracker_statistics_importer: TrackerStatisticsImporter, + + /// The unstable configuration. + #[serde(default = "Settings::default_unstable")] + pub unstable: Option, } impl Default for Settings { @@ -93,6 +99,7 @@ impl Default for Settings { api: Self::default_api(), registration: Self::default_registration(), tracker_statistics_importer: Self::default_tracker_statistics_importer(), + unstable: Self::default_unstable(), } } } @@ -174,6 +181,10 @@ impl Settings { fn default_tracker_statistics_importer() -> TrackerStatisticsImporter { TrackerStatisticsImporter::default() } + + fn default_unstable() -> Option { + None + } } impl Validator for Settings { diff --git a/src/config/v2/unstable.rs b/src/config/v2/unstable.rs new file mode 100644 index 00000000..437c033a --- /dev/null +++ b/src/config/v2/unstable.rs @@ -0,0 +1,55 @@ +use serde::{Deserialize, Serialize}; + +/// Unstable configuration options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Unstable { + /// The casbin configuration used for authorization. + #[serde(default = "Unstable::default_auth")] + pub auth: Option, +} + +impl Default for Unstable { + fn default() -> Self { + Self { + auth: Self::default_auth(), + } + } +} + +impl Unstable { + fn default_auth() -> Option { + None + } +} + +/// Unstable auth configuration options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Auth { + /// The casbin configuration used for authorization. + #[serde(default = "Auth::default_casbin")] + pub casbin: Option, +} + +impl Default for Auth { + fn default() -> Self { + Self { + casbin: Self::default_casbin(), + } + } +} + +impl Auth { + fn default_casbin() -> Option { + None + } +} + +/// Authentication options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Casbin { + /// The model. See . + pub model: String, + + /// The policy. See . + pub policy: String, +} diff --git a/src/services/authorization.rs b/src/services/authorization.rs index 51d2a0d7..dcca38f8 100644 --- a/src/services/authorization.rs +++ b/src/services/authorization.rs @@ -132,40 +132,84 @@ pub struct CasbinEnforcer { impl CasbinEnforcer { /// # Panics /// - /// It panics if the policy and/or model file cannot be loaded - pub async fn new() -> Self { - let casbin_configuration = CasbinConfiguration::new(); + /// Will panic if: + /// + /// - The enforcer can't be created. + /// - The policies can't be loaded. + pub async fn with_default_configuration() -> Self { + let casbin_configuration = CasbinConfiguration::default(); - let model = DefaultModel::from_str(&casbin_configuration.model) + let mut enforcer = Enforcer::new(casbin_configuration.default_model().await, ()) .await - .expect("Error loading the model"); + .expect("Error creating the enforcer"); - // Converts the policy from a string type to a vector - let policy = casbin_configuration - .policy - .lines() - .filter(|line| !line.trim().is_empty()) - .map(|line| line.split(',').map(|s| s.trim().to_owned()).collect::>()) - .collect(); + enforcer + .add_policies(casbin_configuration.policy_lines()) + .await + .expect("Error loading the policy"); + + let enforcer = Arc::new(RwLock::new(enforcer)); + + Self { enforcer } + } - let mut enforcer = Enforcer::new(model, ()).await.expect("Error creating the enforcer"); + /// # Panics + /// + /// Will panic if: + /// + /// - The enforcer can't be created. + /// - The policies can't be loaded. + pub async fn with_configuration(casbin_configuration: CasbinConfiguration) -> Self { + let mut enforcer = Enforcer::new(casbin_configuration.default_model().await, ()) + .await + .expect("Error creating the enforcer"); - enforcer.add_policies(policy).await.expect("Error loading the policy"); + enforcer + .add_policies(casbin_configuration.policy_lines()) + .await + .expect("Error loading the policy"); let enforcer = Arc::new(RwLock::new(enforcer)); Self { enforcer } } } + #[allow(dead_code)] -struct CasbinConfiguration { +pub struct CasbinConfiguration { model: String, policy: String, } impl CasbinConfiguration { - pub fn new() -> Self { - CasbinConfiguration { + #[must_use] + pub fn new(model: &str, policy: &str) -> Self { + Self { + model: model.to_owned(), + policy: policy.to_owned(), + } + } + + /// # Panics + /// + /// It panics if the model cannot be loaded. + async fn default_model(&self) -> DefaultModel { + DefaultModel::from_str(&self.model).await.expect("Error loading the model") + } + + /// Converts the policy from a string type to a vector. + fn policy_lines(&self) -> Vec> { + self.policy + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| line.split(',').map(|s| s.trim().to_owned()).collect::>()) + .collect() + } +} + +impl Default for CasbinConfiguration { + fn default() -> Self { + Self { model: String::from( " [request_definition]