Skip to content

Commit 58a33e7

Browse files
committed
feat: [#615] added authorization for delete torrent action
1 parent e00b0f6 commit 58a33e7

File tree

4 files changed

+66
-171
lines changed

4 files changed

+66
-171
lines changed

casbin/policy.csv

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ p, true, DeleteCategory
33
p, true, GetSettings
44
p, true, GetSettingsSecret
55
p, true, AddTag
6-
p, true, DeleteTag
6+
p, true, DeleteTag
7+
p, true, DeleteTorrent

src/app.rs

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running
116116
torrent_announce_url_repository.clone(),
117117
torrent_tag_repository.clone(),
118118
torrent_listing_generator.clone(),
119+
authorization_service.clone(),
119120
));
120121
let registration_service = Arc::new(user::RegistrationService::new(
121122
configuration.clone(),

src/services/authorization.rs

+56-163
Original file line numberDiff line numberDiff line change
@@ -1,199 +1,92 @@
11
//! Authorization service.
22
use std::sync::Arc;
33

4+
use casbin::prelude::*;
5+
use serde::{Deserialize, Serialize};
6+
use tokio::sync::RwLock;
7+
48
use super::user::Repository;
59
use crate::errors::ServiceError;
610
use crate::models::user::{UserCompact, UserId};
711

12+
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
813
pub enum ACTION {
914
AddCategory,
1015
DeleteCategory,
1116
GetSettings,
1217
GetSettingsSecret,
1318
AddTag,
1419
DeleteTag,
20+
DeleteTorrent,
21+
BanUser,
1522
}
1623

1724
pub struct Service {
1825
user_repository: Arc<Box<dyn Repository>>,
26+
casbin_enforcer: Arc<CasbinEnforcer>,
1927
}
2028

2129
impl Service {
2230
#[must_use]
23-
pub fn new(user_repository: Arc<Box<dyn Repository>>) -> Self {
24-
Self { user_repository }
31+
pub fn new(user_repository: Arc<Box<dyn Repository>>, casbin_enforcer: Arc<CasbinEnforcer>) -> Self {
32+
Self {
33+
user_repository,
34+
casbin_enforcer,
35+
}
2536
}
2637

38+
/// It returns the compact user.
39+
///
2740
/// # Errors
2841
///
29-
/// Will return an error if:
42+
/// It returns an error if there is a database error.
43+
pub async fn get_user(&self, user_id: UserId) -> std::result::Result<UserCompact, ServiceError> {
44+
self.user_repository.get_compact(&user_id).await
45+
}
46+
47+
///Allows or denies an user to perform an action based on the user's privileges
3048
///
31-
/// - There is not any user with the provided `UserId` (when the user id is some).
49+
/// # Errors
50+
///
51+
/// Will return an error if:
52+
/// - There is no user_id found in the request
53+
/// - The user_id is not found in the database
3254
/// - The user is not authorized to perform the action.
33-
pub async fn authorize(&self, action: ACTION, maybe_user_id: Option<UserId>) -> Result<(), ServiceError> {
34-
match action {
35-
ACTION::AddCategory
36-
| ACTION::DeleteCategory
37-
| ACTION::GetSettings
38-
| ACTION::GetSettingsSecret
39-
| ACTION::AddTag
40-
| ACTION::DeleteTag => match maybe_user_id {
41-
Some(user_id) => {
42-
let user = self.get_user(user_id).await?;
43-
44-
if !user.administrator {
45-
return Err(ServiceError::Unauthorized);
46-
}
47-
48-
Ok(())
55+
pub async fn authorize(&self, action: ACTION, maybe_user_id: Option<UserId>) -> std::result::Result<(), ServiceError> {
56+
match maybe_user_id {
57+
Some(user_id) => {
58+
let user_guard = self.get_user(user_id).await.map_err(|_| ServiceError::UserNotFound);
59+
// the user that wants to access a resource.
60+
let role = user_guard.unwrap().administrator;
61+
62+
// the user that wants to access a resource.
63+
let sub = role.to_string();
64+
65+
let act = action; // the operation that the user performs on the resource.
66+
67+
let enforcer = self.casbin_enforcer.enforcer.read().await;
68+
/* let enforcer = self.casbin_enforcer.clone();
69+
let enforcer_lock = enforcer.enforcer.read().await; */
70+
let authorize = enforcer.enforce((sub, act)).unwrap();
71+
match authorize {
72+
true => Ok(()),
73+
false => Err(ServiceError::Unauthorized),
4974
}
50-
None => Err(ServiceError::Unauthorized),
51-
},
75+
}
76+
None => Err(ServiceError::Unauthorized),
5277
}
5378
}
54-
55-
async fn get_user(&self, user_id: UserId) -> Result<UserCompact, ServiceError> {
56-
self.user_repository.get_compact(&user_id).await
57-
}
5879
}
59-
#[allow(unused_imports)]
60-
#[cfg(test)]
61-
mod test {
62-
use std::str::FromStr;
63-
use std::sync::Arc;
6480

65-
use mockall::predicate;
66-
67-
use crate::databases::database;
68-
use crate::errors::ServiceError;
69-
use crate::models::user::{User, UserCompact};
70-
use crate::services::authorization::{Service, ACTION};
71-
use crate::services::user::{MockRepository, Repository};
72-
use crate::web::api::client::v1::random::string;
73-
74-
#[tokio::test]
75-
async fn a_guest_user_should_not_be_able_to_add_categories() {
76-
let test_user_id = 1;
77-
78-
let mut mock_repository = MockRepository::new();
79-
mock_repository
80-
.expect_get_compact()
81-
.with(predicate::eq(test_user_id))
82-
.times(1)
83-
.returning(|_| Err(ServiceError::UserNotFound));
84-
85-
let service = Service::new(Arc::new(Box::new(mock_repository)));
86-
assert_eq!(
87-
service.authorize(ACTION::AddCategory, Some(test_user_id)).await,
88-
Err(ServiceError::UserNotFound)
89-
);
90-
}
91-
92-
#[tokio::test]
93-
async fn a_registered_user_should_not_be_able_to_add_categories() {
94-
let test_user_id = 2;
95-
96-
let mut mock_repository = MockRepository::new();
97-
mock_repository
98-
.expect_get_compact()
99-
.with(predicate::eq(test_user_id))
100-
.times(1)
101-
.returning(move |_| {
102-
Ok(UserCompact {
103-
user_id: test_user_id,
104-
username: "non_admin_user".to_string(),
105-
administrator: false,
106-
})
107-
});
108-
109-
let service = Service::new(Arc::new(Box::new(mock_repository)));
110-
assert_eq!(
111-
service.authorize(ACTION::AddCategory, Some(test_user_id)).await,
112-
Err(ServiceError::Unauthorized)
113-
);
114-
}
115-
116-
#[tokio::test]
117-
async fn an_admin_user_should_be_able_to_add_categories() {
118-
let test_user_id = 3;
119-
120-
let mut mock_repository = MockRepository::new();
121-
mock_repository
122-
.expect_get_compact()
123-
.with(predicate::eq(test_user_id))
124-
.times(1)
125-
.returning(move |_| {
126-
Ok(UserCompact {
127-
user_id: test_user_id,
128-
username: "admin_user".to_string(),
129-
administrator: true,
130-
})
131-
});
132-
133-
let service = Service::new(Arc::new(Box::new(mock_repository)));
134-
assert_eq!(service.authorize(ACTION::AddCategory, Some(test_user_id)).await, Ok(()));
135-
}
136-
137-
#[tokio::test]
138-
async fn a_guest_user_should_not_be_able_to_delete_categories() {
139-
let test_user_id = 4;
140-
141-
let mut mock_repository = MockRepository::new();
142-
mock_repository
143-
.expect_get_compact()
144-
.with(predicate::eq(test_user_id))
145-
.times(1)
146-
.returning(|_| Err(ServiceError::UserNotFound));
147-
148-
let service = Service::new(Arc::new(Box::new(mock_repository)));
149-
assert_eq!(
150-
service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await,
151-
Err(ServiceError::UserNotFound)
152-
);
153-
}
154-
155-
#[tokio::test]
156-
async fn a_registered_user_should_not_be_able_to_delete_categories() {
157-
let test_user_id = 5;
158-
159-
let mut mock_repository = MockRepository::new();
160-
mock_repository
161-
.expect_get_compact()
162-
.with(predicate::eq(test_user_id))
163-
.times(1)
164-
.returning(move |_| {
165-
Ok(UserCompact {
166-
user_id: test_user_id,
167-
username: "non_admin_user".to_string(),
168-
administrator: false,
169-
})
170-
});
171-
172-
let service = Service::new(Arc::new(Box::new(mock_repository)));
173-
assert_eq!(
174-
service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await,
175-
Err(ServiceError::Unauthorized)
176-
);
177-
}
178-
179-
#[tokio::test]
180-
async fn an_admin_user_should_be_able_to_delete_categories() {
181-
let test_user_id = 6;
182-
183-
let mut mock_repository = MockRepository::new();
184-
mock_repository
185-
.expect_get_compact()
186-
.with(predicate::eq(test_user_id))
187-
.times(1)
188-
.returning(move |_| {
189-
Ok(UserCompact {
190-
user_id: test_user_id,
191-
username: "admin_user".to_string(),
192-
administrator: true,
193-
})
194-
});
81+
pub struct CasbinEnforcer {
82+
enforcer: Arc<RwLock<Enforcer>>, //Arc<tokio::sync::RwLock<casbin::Enforcer>>
83+
}
19584

196-
let service = Service::new(Arc::new(Box::new(mock_repository)));
197-
assert_eq!(service.authorize(ACTION::DeleteCategory, Some(test_user_id)).await, Ok(()));
85+
impl CasbinEnforcer {
86+
pub async fn new() -> Self {
87+
let enforcer = Enforcer::new("casbin/model.conf", "casbin/policy.csv").await.unwrap();
88+
let enforcer = Arc::new(RwLock::new(enforcer));
89+
//casbin_enforcer.enable_log(true);
90+
Self { enforcer }
19891
}
19992
}

src/services/torrent.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use serde_derive::{Deserialize, Serialize};
55
use tracing::debug;
66
use url::Url;
77

8+
use super::authorization::{self, ACTION};
89
use super::category::DbCategoryRepository;
910
use crate::config::{Configuration, TrackerMode};
1011
use crate::databases::database::{Database, Error, Sorting};
@@ -34,6 +35,7 @@ pub struct Index {
3435
torrent_announce_url_repository: Arc<DbTorrentAnnounceUrlRepository>,
3536
torrent_tag_repository: Arc<DbTorrentTagRepository>,
3637
torrent_listing_generator: Arc<DbTorrentListingGenerator>,
38+
authorization_service: Arc<authorization::Service>,
3739
}
3840

3941
pub struct AddTorrentRequest {
@@ -90,6 +92,7 @@ impl Index {
9092
torrent_announce_url_repository: Arc<DbTorrentAnnounceUrlRepository>,
9193
torrent_tag_repository: Arc<DbTorrentTagRepository>,
9294
torrent_listing_repository: Arc<DbTorrentListingGenerator>,
95+
authorization_service: Arc<authorization::Service>,
9396
) -> Self {
9497
Self {
9598
configuration,
@@ -104,6 +107,7 @@ impl Index {
104107
torrent_announce_url_repository,
105108
torrent_tag_repository,
106109
torrent_listing_generator: torrent_listing_repository,
110+
authorization_service,
107111
}
108112
}
109113

@@ -289,13 +293,9 @@ impl Index {
289293
/// * Unable to get the torrent listing from it's ID.
290294
/// * Unable to delete the torrent from the database.
291295
pub async fn delete_torrent(&self, info_hash: &InfoHash, user_id: &UserId) -> Result<DeletedTorrentResponse, ServiceError> {
292-
let user = self.user_repository.get_compact(user_id).await?;
293-
294-
// Only administrator can delete torrents.
295-
// todo: move this to an authorization service.
296-
if !user.administrator {
297-
return Err(ServiceError::Unauthorized);
298-
}
296+
self.authorization_service
297+
.authorize(ACTION::DeleteTorrent, Some(*user_id))
298+
.await?;
299299

300300
let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?;
301301

0 commit comments

Comments
 (0)