Skip to content

Commit

Permalink
feat: [#448] new authorization service
Browse files Browse the repository at this point in the history
  • Loading branch information
mario-nt committed Jun 3, 2024
1 parent b1d5536 commit 9ebd7b5
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 9 deletions.
16 changes: 10 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use crate::services::torrent::{
DbCanonicalInfoHashGroupRepository, DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository,
DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository,
};
use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository};
use crate::services::{proxy, settings, torrent};
use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository, Repository};
use crate::services::{authorization, proxy, settings, torrent};
use crate::tracker::statistics_importer::StatisticsImporter;
use crate::web::api::server::signals::Halted;
use crate::web::api::server::v1::auth::Authentication;
Expand Down Expand Up @@ -74,7 +74,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running
// Repositories
let category_repository = Arc::new(DbCategoryRepository::new(database.clone()));
let tag_repository = Arc::new(DbTagRepository::new(database.clone()));
let user_repository = Arc::new(DbUserRepository::new(database.clone()));
let user_repository: Arc<Box<dyn Repository>> = Arc::new(Box::new(DbUserRepository::new(database.clone())));
let user_authentication_repository = Arc::new(DbUserAuthenticationRepository::new(database.clone()));
let user_profile_repository = Arc::new(DbUserProfileRepository::new(database.clone()));
let torrent_repository = Arc::new(DbTorrentRepository::new(database.clone()));
Expand All @@ -87,15 +87,19 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running
let banned_user_list = Arc::new(DbBannedUserList::new(database.clone()));

// Services
let authorization_service = Arc::new(authorization::Service::new(user_repository.clone()));
let tracker_service = Arc::new(tracker::service::Service::new(configuration.clone(), database.clone()).await);
let tracker_statistics_importer =
Arc::new(StatisticsImporter::new(configuration.clone(), tracker_service.clone(), database.clone()).await);
let mailer_service = Arc::new(mailer::Service::new(configuration.clone()).await);
let image_cache_service: Arc<ImageCacheService> = Arc::new(ImageCacheService::new(configuration.clone()).await);
let category_service = Arc::new(category::Service::new(category_repository.clone(), user_repository.clone()));
let tag_service = Arc::new(tag::Service::new(tag_repository.clone(), user_repository.clone()));
let category_service = Arc::new(category::Service::new(
category_repository.clone(),
authorization_service.clone(),
));
let tag_service = Arc::new(tag::Service::new(tag_repository.clone(), authorization_service.clone()));
let proxy_service = Arc::new(proxy::Service::new(image_cache_service.clone(), user_repository.clone()));
let settings_service = Arc::new(settings::Service::new(configuration.clone(), user_repository.clone()));
let settings_service = Arc::new(settings::Service::new(configuration.clone(), authorization_service.clone()));
let torrent_index = Arc::new(torrent::Index::new(
configuration.clone(),
tracker_statistics_importer.clone(),
Expand Down
7 changes: 4 additions & 3 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ use crate::services::torrent::{
DbCanonicalInfoHashGroupRepository, DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository,
DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository,
};
use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository};
use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, Repository};
use crate::services::{proxy, settings, torrent};
use crate::tracker::statistics_importer::StatisticsImporter;
use crate::web::api::server::v1::auth::Authentication;
use crate::{mailer, tracker};

pub type Username = String;

pub struct AppData {
Expand All @@ -30,7 +31,7 @@ pub struct AppData {
// Repositories
pub category_repository: Arc<DbCategoryRepository>,
pub tag_repository: Arc<DbTagRepository>,
pub user_repository: Arc<DbUserRepository>,
pub user_repository: Arc<Box<dyn Repository>>,
pub user_authentication_repository: Arc<DbUserAuthenticationRepository>,
pub user_profile_repository: Arc<DbUserProfileRepository>,
pub torrent_repository: Arc<DbTorrentRepository>,
Expand Down Expand Up @@ -66,7 +67,7 @@ impl AppData {
// Repositories
category_repository: Arc<DbCategoryRepository>,
tag_repository: Arc<DbTagRepository>,
user_repository: Arc<DbUserRepository>,
user_repository: Arc<Box<dyn Repository>>,
user_authentication_repository: Arc<DbUserAuthenticationRepository>,
user_profile_repository: Arc<DbUserProfileRepository>,
torrent_repository: Arc<DbTorrentRepository>,
Expand Down
199 changes: 199 additions & 0 deletions src/services/authorization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//! Authorization service.
use std::sync::Arc;

use super::user::Repository;
use crate::errors::ServiceError;
use crate::models::user::{UserCompact, UserId};

pub enum ACTION {
AddCategory,
DeleteCategory,
GetSettings,
GetSettingsSecret,
AddTag,
DeleteTag,
}

pub struct Service {
user_repository: Arc<Box<dyn Repository>>,
}

impl Service {
#[must_use]
pub fn new(user_repository: Arc<Box<dyn Repository>>) -> Self {
Self { user_repository }
}

/// # Errors
///
/// Will return an error if:
///
/// - There is not any user with the provided `UserId` (when the user id is some).
/// - The user is not authorized to perform the action.
pub async fn authorize(&self, action: ACTION, maybe_user_id: Option<UserId>) -> Result<(), ServiceError> {
match action {
ACTION::AddCategory
| ACTION::DeleteCategory
| ACTION::GetSettings
| ACTION::GetSettingsSecret
| ACTION::AddTag
| ACTION::DeleteTag => match maybe_user_id {
Some(user_id) => {
let user = self.get_user(user_id).await?;

if !user.administrator {
return Err(ServiceError::Unauthorized);
}

Ok(())
}
None => Err(ServiceError::Unauthorized),
},
}
}

async fn get_user(&self, user_id: UserId) -> Result<UserCompact, ServiceError> {
self.user_repository.get_compact(&user_id).await
}
}
#[allow(unused_imports)]
#[cfg(test)]
mod test {
use std::str::FromStr;
use std::sync::Arc;

use mockall::predicate;

use crate::databases::database;
use crate::errors::ServiceError;
use crate::models::user::{User, UserCompact};
use crate::services::authorization::{Service, ACTION};
use crate::services::user::{MockRepository, Repository};
use crate::web::api::client::v1::random::string;

#[tokio::test]
async fn a_guest_user_should_not_be_able_to_add_categories() {
let test_user_id = 1;

let mut mock_repository = MockRepository::new();
mock_repository
.expect_get_compact()
.with(predicate::eq(test_user_id))
.times(1)
.returning(|_| Err(ServiceError::UserNotFound));

let service = Service::new(Arc::new(Box::new(mock_repository)));
assert_eq!(
service.authorize(ACTION::AddCategory, Some(test_user_id)).await,
Err(ServiceError::UserNotFound)
);
}

#[tokio::test]
async fn a_registered_user_should_not_be_able_to_add_categories() {
let test_user_id = 2;

let mut mock_repository = MockRepository::new();
mock_repository
.expect_get_compact()
.with(predicate::eq(test_user_id))
.times(1)
.returning(move |_| {
Ok(UserCompact {
user_id: test_user_id,
username: "non_admin_user".to_string(),
administrator: false,
})
});

let service = Service::new(Arc::new(Box::new(mock_repository)));
assert_eq!(
service.authorize(ACTION::AddCategory, Some(test_user_id)).await,
Err(ServiceError::Unauthorized)
);
}

#[tokio::test]
async fn an_admin_user_should_be_able_to_add_categories() {
let test_user_id = 3;

let mut mock_repository = MockRepository::new();
mock_repository
.expect_get_compact()
.with(predicate::eq(test_user_id))
.times(1)
.returning(move |_| {
Ok(UserCompact {
user_id: test_user_id,
username: "admin_user".to_string(),
administrator: true,
})
});

let service = Service::new(Arc::new(Box::new(mock_repository)));
assert_eq!(service.authorize(ACTION::AddCategory, Some(test_user_id)).await, Ok(()));
}

#[tokio::test]
async fn a_guest_user_should_not_be_able_to_delete_categories() {
let test_user_id = 4;

let mut mock_repository = MockRepository::new();
mock_repository
.expect_get_compact()
.with(predicate::eq(test_user_id))
.times(1)
.returning(|_| Err(ServiceError::UserNotFound));

let service = Service::new(Arc::new(Box::new(mock_repository)));
assert_eq!(
service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await,
Err(ServiceError::UserNotFound)
);
}

#[tokio::test]
async fn a_registered_user_should_not_be_able_to_delete_categories() {
let test_user_id = 5;

let mut mock_repository = MockRepository::new();
mock_repository
.expect_get_compact()
.with(predicate::eq(test_user_id))
.times(1)
.returning(move |_| {
Ok(UserCompact {
user_id: test_user_id,
username: "non_admin_user".to_string(),
administrator: false,
})
});

let service = Service::new(Arc::new(Box::new(mock_repository)));
assert_eq!(
service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await,
Err(ServiceError::Unauthorized)
);
}

#[tokio::test]
async fn an_admin_user_should_be_able_to_delete_categories() {
let test_user_id = 6;

let mut mock_repository = MockRepository::new();
mock_repository
.expect_get_compact()
.with(predicate::eq(test_user_id))
.times(1)
.returning(move |_| {
Ok(UserCompact {
user_id: test_user_id,
username: "admin_user".to_string(),
administrator: true,
})
});

let service = Service::new(Arc::new(Box::new(mock_repository)));
assert_eq!(service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, Ok(()));
}
}
1 change: 1 addition & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! App services.
pub mod about;
pub mod authentication;
pub mod authorization;
pub mod category;
pub mod hasher;
pub mod proxy;
Expand Down

0 comments on commit 9ebd7b5

Please sign in to comment.