Skip to content

Commit

Permalink
feat: [#702] allow overwriting casbin configuration
Browse files Browse the repository at this point in the history
This is an unsatble feature. You can overwrite casbin configuration to
change permissions for roles:  guest, registered and admin.

You can do it by adding this toml file config section:

```toml
[unstable.auth.casbin]
model = """
[request_definition]
r = role, action

[policy_definition]
p = role, action

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.role == p.role && r.action == p.action
"""

policy = """
admin, GetAboutPage
admin, GetLicensePage
admin, AddCategory
admin, DeleteCategory
admin, GetCategories
admin, GetImageByUrl
admin, GetSettings
admin, GetSettingsSecret
admin, GetPublicSettings
admin, AddTag
admin, DeleteTag
admin, GetTags
admin, AddTorrent
admin, GetTorrent
admin, DeleteTorrent
admin, GetTorrentInfo
admin, GenerateTorrentInfoListing
admin, GetCanonicalInfoHash
admin, ChangePassword
admin, BanUser
registered, GetAboutPage
registered, GetLicensePage
registered, GetCategories
registered, GetImageByUrl
registered, GetPublicSettings
registered, GetTags
registered, AddTorrent
registered, GetTorrent
registered, GetTorrentInfo
registered, GenerateTorrentInfoListing
registered, GetCanonicalInfoHash
registered, ChangePassword
guest, GetAboutPage
guest, GetLicensePage
guest, GetCategories
guest, GetPublicSettings
guest, GetTags
guest, GetTorrent
guest, GetTorrentInfo
guest, GenerateTorrentInfoListing
guest, GetCanonicalInfoHash
"""
```

For example, if you wnat to force users to login to see the torrent list
you can remove the following line from the policy:

```
guest, GenerateTorrentInfoListing
```

NOTICE: This is an unstable feature. It will panic with wrong
casbin configuration, invalid roles, etcetera.
  • Loading branch information
josecelano committed Aug 7, 2024
1 parent a39ad21 commit c1a5c25
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 18 deletions.
2 changes: 2 additions & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ binascii
btih
buildx
camino
Casbin
chrono
clippy
codecov
Expand Down Expand Up @@ -51,6 +52,7 @@ luckythelab
mailcatcher
mandelbrotset
metainfo
Mgmt
migth
nanos
NCCA
Expand Down
17 changes: 16 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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.
Expand All @@ -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()));
Expand Down
11 changes: 11 additions & 0 deletions src/config/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Unstable>,
}

impl Default for Settings {
Expand All @@ -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(),
}
}
}
Expand Down Expand Up @@ -174,6 +181,10 @@ impl Settings {
fn default_tracker_statistics_importer() -> TrackerStatisticsImporter {
TrackerStatisticsImporter::default()
}

fn default_unstable() -> Option<Unstable> {
None
}
}

impl Validator for Settings {
Expand Down
55 changes: 55 additions & 0 deletions src/config/v2/unstable.rs
Original file line number Diff line number Diff line change
@@ -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<Auth>,
}

impl Default for Unstable {
fn default() -> Self {
Self {
auth: Self::default_auth(),
}
}
}

impl Unstable {
fn default_auth() -> Option<Auth> {
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<Casbin>,
}

impl Default for Auth {
fn default() -> Self {
Self {
casbin: Self::default_casbin(),
}
}
}

impl Auth {
fn default_casbin() -> Option<Casbin> {
None
}
}

/// Authentication options.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Casbin {
/// The model. See <https://casbin.org>.
pub model: String,

/// The policy. See <https://casbin.org>.
pub policy: String,
}
78 changes: 61 additions & 17 deletions src/services/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<String>>())
.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<Vec<String>> {
self.policy
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| line.split(',').map(|s| s.trim().to_owned()).collect::<Vec<String>>())
.collect()
}
}

impl Default for CasbinConfiguration {
fn default() -> Self {
Self {
model: String::from(
"
[request_definition]
Expand Down

0 comments on commit c1a5c25

Please sign in to comment.