Skip to content

Commit a39ad21

Browse files
committed
Merge #662: #615: Authorization for public handlers
f38b628 refactor: [#615] new authorization error for guest users (Mario) d8b3ee2 refactor: [#615] change test http return code (Mario) 0360517 refactor: [#615] changed status code so tests pass (Mario) 6010155 refactor: [#615] handlers and functions now use optional user id (Mario) c22c919 refactor: [#615] change password handler and function now use optional user id (Mario) dbcd0e1 refactor: [#615] added missing error to handler comments (Mario) 00c293c refactor: [#615] added missing error to function comments (Mario) e527867 fix: [#615] adjusted http error code for test so it pass (Mario) 42fb031 refactor: [#615] renamed error to be more human friendly (Mario) 836c94d refactor: [#615] upload torrent handler now uses an optional user id (Mario) 935eb53 refactor: [#615] rearrange actions (Mario) 93b15ac refactor: [#615] renamed variables to maybe_user_id (Mario) e614e2f feat: [#615] authorization layer added to the change password method of the user service (Mario) 543d804 refactor: [#615] redirect to details url with infohash method now calls the torrent service that implements the authorization layer (Mario) f8e7570 feat: [#615] authorization layer added to the get torrent info method of the torrent service (Mario) 1342dce feat: [#615] authorization layer implemented for the get torrents handler (Mario) f264e75 fix: [#615] fix unfinished comment (Mario) d56d66c refactor: [#615] download torrent handler now calls the torrent service to get the canonical infohash (Mario) b3ffc9f feat: [#615] new get canonical info hash method (Mario) 7bb6ff7 feat: [#615] authorization layer implemented for the get torrent method of the torrent service (Mario) b5171bf feat: [#615] authorization layer added for add torrent method of the torrent service (Mario) 1c607c2 refactor: [#615] added optional logged in user to public handlers and methods (Mario) b483c8e refactor: [#615] renamed variable (Mario) 2880f7c feat: [#615] added authorization layer for get public settings method of the settings service (Mario) 530f37a feat: [#615] admin user is now authorize to see proxied images (Mario) d1d2941 refactor: [#615] fix linting errors (Mario) 056cb83 refactor: [#615] methods used by the proxy service now use the user id instead of the user compact type (Mario) 173f43d feat: [#615] authorization layer implemented for the proxy service (Mario) 4f6ea18 refactor: [#615] new action and reordered old ones for cohesion (Mario) 0ac5f9b refactor: [#615] get all categories handler now calls the category service which uses the authorization layer (Mario) 839a00d feat: [#615] New get categories service method (Mario) fe11c2f refactor: [#615] added comments for new functions (Mario) cd8f609 feat: [#615] authorization implemented for get tags method (Mario) eee3349 refactor: [#615] new get tags service method (Mario) 6f8de80 feat: [#615] authorization service implemented for the about service (Mario) a757c5c refactor: [#615] new about service and minor refactor to existing code (Mario) Pull request description: Parent issue: #615 ACKs for top commit: josecelano: ACK f38b628 Tree-SHA512: d8d710af481b2480b0035ad13feaab15e6299f5767f4d68837a4a05c237fd3e0bdbccdfa46c0c625b11e7109dcb73879036debf7a51b7d83c856403878ce70d2
2 parents 403c549 + f38b628 commit a39ad21

File tree

19 files changed

+416
-186
lines changed

19 files changed

+416
-186
lines changed

src/app.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::services::torrent::{
1818
DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository,
1919
};
2020
use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, DbUserRepository, Repository};
21-
use crate::services::{authorization, proxy, settings, torrent};
21+
use crate::services::{about, authorization, proxy, settings, torrent};
2222
use crate::tracker::statistics_importer::StatisticsImporter;
2323
use crate::web::api::server::signals::Halted;
2424
use crate::web::api::server::v1::auth::Authentication;
@@ -101,7 +101,10 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running
101101
authorization_service.clone(),
102102
));
103103
let tag_service = Arc::new(tag::Service::new(tag_repository.clone(), authorization_service.clone()));
104-
let proxy_service = Arc::new(proxy::Service::new(image_cache_service.clone(), user_repository.clone()));
104+
let proxy_service = Arc::new(proxy::Service::new(
105+
image_cache_service.clone(),
106+
authorization_service.clone(),
107+
));
105108
let settings_service = Arc::new(settings::Service::new(configuration.clone(), authorization_service.clone()));
106109
let torrent_index = Arc::new(torrent::Index::new(
107110
configuration.clone(),
@@ -127,6 +130,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running
127130
let profile_service = Arc::new(user::ProfileService::new(
128131
configuration.clone(),
129132
user_authentication_repository.clone(),
133+
authorization_service.clone(),
130134
));
131135
let ban_service = Arc::new(user::BanService::new(
132136
user_profile_repository.clone(),
@@ -141,6 +145,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running
141145
user_authentication_repository.clone(),
142146
));
143147

148+
let about_service = Arc::new(about::Service::new(authorization_service.clone()));
149+
144150
// Build app container
145151

146152
let app_data = Arc::new(AppData::new(
@@ -174,6 +180,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running
174180
registration_service,
175181
profile_service,
176182
ban_service,
183+
about_service,
177184
));
178185

179186
// Start cronjob to import tracker torrent data and updating

src/cache/image/manager.rs

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use tokio::sync::RwLock;
77

88
use crate::cache::BytesCache;
99
use crate::config::Configuration;
10-
use crate::models::user::UserCompact;
10+
use crate::models::user::UserId;
1111

1212
pub enum Error {
1313
UrlIsUnreachable,
@@ -125,33 +125,26 @@ impl ImageCacheService {
125125
/// # Errors
126126
///
127127
/// Return a `Error::Unauthenticated` if the user has not been authenticated.
128-
pub async fn get_image_by_url(&self, url: &str, opt_user: Option<UserCompact>) -> Result<Bytes, Error> {
128+
pub async fn get_image_by_url(&self, url: &str, user_id: UserId) -> Result<Bytes, Error> {
129129
if let Some(entry) = self.image_cache.read().await.get(url).await {
130130
return Ok(entry.bytes);
131131
}
132+
self.check_user_quota(&user_id).await?;
132133

133-
match opt_user {
134-
None => Err(Error::Unauthenticated),
134+
let image_bytes = self.get_image_from_url_as_bytes(url).await?;
135135

136-
Some(user) => {
137-
self.check_user_quota(&user).await?;
136+
self.check_image_size(&image_bytes).await?;
138137

139-
let image_bytes = self.get_image_from_url_as_bytes(url).await?;
138+
// These two functions could be executed after returning the image to the client,
139+
// but than we would need a dedicated task or thread that executes these functions.
140+
// This can be problematic if a task is spawned after every user request.
141+
// Since these functions execute very fast, I don't see a reason to further optimize this.
142+
// For now.
143+
self.update_image_cache(url, &image_bytes).await?;
140144

141-
self.check_image_size(&image_bytes).await?;
145+
self.update_user_quota(&user_id, image_bytes.len()).await?;
142146

143-
// These two functions could be executed after returning the image to the client,
144-
// but than we would need a dedicated task or thread that executes these functions.
145-
// This can be problematic if a task is spawned after every user request.
146-
// Since these functions execute very fast, I don't see a reason to further optimize this.
147-
// For now.
148-
self.update_image_cache(url, &image_bytes).await?;
149-
150-
self.update_user_quota(&user, image_bytes.len()).await?;
151-
152-
Ok(image_bytes)
153-
}
154-
}
147+
Ok(image_bytes)
155148
}
156149

157150
async fn get_image_from_url_as_bytes(&self, url: &str) -> Result<Bytes, Error> {
@@ -176,8 +169,8 @@ impl ImageCacheService {
176169
res.bytes().await.map_err(|_| Error::UrlIsNotAnImage)
177170
}
178171

179-
async fn check_user_quota(&self, user: &UserCompact) -> Result<(), Error> {
180-
if let Some(quota) = self.user_quotas.read().await.get(&user.user_id) {
172+
async fn check_user_quota(&self, user_id: &UserId) -> Result<(), Error> {
173+
if let Some(quota) = self.user_quotas.read().await.get(user_id) {
181174
if quota.is_reached() {
182175
return Err(Error::UserQuotaMet);
183176
}
@@ -211,24 +204,24 @@ impl ImageCacheService {
211204
Ok(())
212205
}
213206

214-
async fn update_user_quota(&self, user: &UserCompact, amount: usize) -> Result<(), Error> {
207+
async fn update_user_quota(&self, user_id: &UserId, amount: usize) -> Result<(), Error> {
215208
let settings = self.cfg.settings.read().await;
216209

217210
let mut quota = self
218211
.user_quotas
219212
.read()
220213
.await
221-
.get(&user.user_id)
214+
.get(user_id)
222215
.cloned()
223216
.unwrap_or(ImageCacheQuota::new(
224-
user.user_id,
217+
*user_id,
225218
settings.image_cache.user_quota_bytes,
226219
settings.image_cache.user_quota_period_seconds,
227220
));
228221

229222
let _ = quota.add_usage(amount);
230223

231-
let _ = self.user_quotas.write().await.insert(user.user_id, quota);
224+
let _ = self.user_quotas.write().await.insert(*user_id, quota);
232225

233226
Ok(())
234227
}

src/common.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::services::torrent::{
1111
DbTorrentListingGenerator, DbTorrentRepository, DbTorrentTagRepository,
1212
};
1313
use crate::services::user::{self, DbBannedUserList, DbUserProfileRepository, Repository};
14-
use crate::services::{proxy, settings, torrent};
14+
use crate::services::{about, proxy, settings, torrent};
1515
use crate::tracker::statistics_importer::StatisticsImporter;
1616
use crate::web::api::server::v1::auth::Authentication;
1717
use crate::{mailer, tracker};
@@ -51,6 +51,7 @@ pub struct AppData {
5151
pub registration_service: Arc<user::RegistrationService>,
5252
pub profile_service: Arc<user::ProfileService>,
5353
pub ban_service: Arc<user::BanService>,
54+
pub about_service: Arc<about::Service>,
5455
}
5556

5657
impl AppData {
@@ -88,6 +89,7 @@ impl AppData {
8889
registration_service: Arc<user::RegistrationService>,
8990
profile_service: Arc<user::ProfileService>,
9091
ban_service: Arc<user::BanService>,
92+
about_service: Arc<about::Service>,
9193
) -> AppData {
9294
AppData {
9395
cfg,
@@ -122,6 +124,7 @@ impl AppData {
122124
registration_service,
123125
profile_service,
124126
ban_service,
127+
about_service,
125128
}
126129
}
127130
}

src/errors.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,12 @@ pub enum ServiceError {
109109
InvalidTag,
110110

111111
#[display(fmt = "Unauthorized action.")]
112-
Unauthorized,
112+
UnauthorizedAction,
113+
114+
#[display(
115+
fmt = "Unauthorized actions for guest users. Try logging in to check if you have permission to perform the action"
116+
)]
117+
UnauthorizedActionForGuests,
113118

114119
#[display(fmt = "This torrent already exists in our database.")]
115120
InfoHashAlreadyExists,
@@ -300,7 +305,8 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode {
300305
ServiceError::MissingMandatoryMetadataFields => StatusCode::BAD_REQUEST,
301306
ServiceError::InvalidCategory => StatusCode::BAD_REQUEST,
302307
ServiceError::InvalidTag => StatusCode::BAD_REQUEST,
303-
ServiceError::Unauthorized => StatusCode::FORBIDDEN,
308+
ServiceError::UnauthorizedAction => StatusCode::FORBIDDEN,
309+
ServiceError::UnauthorizedActionForGuests => StatusCode::UNAUTHORIZED,
304310
ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST,
305311
ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::CONFLICT,
306312
ServiceError::OriginalInfoHashAlreadyExists => StatusCode::CONFLICT,

src/services/about.rs

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
11
//! Templates for "about" static pages.
22
3-
#[must_use]
4-
pub fn index_page() -> String {
5-
page()
3+
use std::sync::Arc;
4+
5+
use super::authorization::{self, ACTION};
6+
use crate::errors::ServiceError;
7+
use crate::models::user::UserId;
8+
9+
pub struct Service {
10+
authorization_service: Arc<authorization::Service>,
611
}
712

8-
#[must_use]
9-
pub fn page() -> String {
10-
r#"
13+
impl Service {
14+
#[must_use]
15+
pub fn new(authorization_service: Arc<authorization::Service>) -> Service {
16+
Service { authorization_service }
17+
}
18+
19+
/// Returns the html with the about page
20+
///
21+
/// # Errors
22+
///
23+
/// It returns an error if:
24+
///
25+
/// * The user does not have the required permissions.
26+
/// * There is an error authorizing the action.
27+
pub async fn get_about_page(&self, maybe_user_id: Option<UserId>) -> Result<String, ServiceError> {
28+
self.authorization_service
29+
.authorize(ACTION::GetAboutPage, maybe_user_id)
30+
.await?;
31+
32+
let html = r#"
1133
<html>
1234
<head>
1335
<title>About</title>
@@ -23,37 +45,52 @@ pub fn page() -> String {
2345
<a href="./about/license">license</a>
2446
</footer>
2547
</html>
26-
"#
27-
.to_string()
28-
}
29-
30-
#[must_use]
31-
pub fn license_page() -> String {
32-
r#"
33-
<html>
34-
<head>
35-
<title>Licensing</title>
36-
</head>
37-
<body style="margin-left: auto;margin-right: auto;max-width: 30em;">
38-
<h1>Torrust Index</h1>
39-
40-
<h2>Licensing</h2>
41-
42-
<h3>Multiple Licenses</h3>
43-
44-
<p>This repository has multiple licenses depending on the content type, the date of contributions or stemming from external component licenses that were not developed by any of Torrust team members or Torrust repository contributors.</p>
48+
"#;
4549

46-
<p>The two main applicable license to most of its content are:</p>
50+
Ok(html.to_string())
51+
}
4752

48-
<p>- For Code -- <a href="https://github.com/torrust/torrust-index/blob/main/licensing/agpl-3.0.md">agpl-3.0</a></p>
53+
/// Returns the html with the license page
54+
///
55+
/// # Errors
56+
///
57+
/// It returns an error if:
58+
///
59+
/// * The user does not have the required permissions.
60+
/// * There is an error authorizing the action.
61+
pub async fn get_license_page(&self, maybe_user_id: Option<UserId>) -> Result<String, ServiceError> {
62+
self.authorization_service
63+
.authorize(ACTION::GetLicensePage, maybe_user_id)
64+
.await?;
4965

50-
<p>- For Media (Images, etc.) -- <a href="https://github.com/torrust/torrust-index/blob/main/licensing/cc-by-sa.md">cc-by-sa</a></p>
66+
let html = r#"
67+
<html>
68+
<head>
69+
<title>Licensing</title>
70+
</head>
71+
<body style="margin-left: auto;margin-right: auto;max-width: 30em;">
72+
<h1>Torrust Index</h1>
73+
74+
<h2>Licensing</h2>
75+
76+
<h3>Multiple Licenses</h3>
77+
78+
<p>This repository has multiple licenses depending on the content type, the date of contributions or stemming from external component licenses that were not developed by any of Torrust team members or Torrust repository contributors.</p>
79+
80+
<p>The two main applicable license to most of its content are:</p>
81+
82+
<p>- For Code -- <a href="https://github.com/torrust/torrust-index/blob/main/licensing/agpl-3.0.md">agpl-3.0</a></p>
83+
84+
<p>- For Media (Images, etc.) -- <a href="https://github.com/torrust/torrust-index/blob/main/licensing/cc-by-sa.md">cc-by-sa</a></p>
85+
86+
<p>If you want to read more about all the licenses and how they apply please refer to the <a href="https://github.com/torrust/torrust-index/blob/develop/licensing/contributor_agreement_v01.md">contributor agreement</a>.</p>
87+
</body>
88+
<footer style="padding: 1.25em 0;border-top: dotted 1px;">
89+
<a href="../about">about</a>
90+
</footer>
91+
</html>
92+
"#;
5193

52-
<p>If you want to read more about all the licenses and how they apply please refer to the <a href="https://github.com/torrust/torrust-index/blob/develop/licensing/contributor_agreement_v01.md">contributor agreement</a>.</p>
53-
</body>
54-
<footer style="padding: 1.25em 0;border-top: dotted 1px;">
55-
<a href="../about">about</a>
56-
</footer>
57-
</html>
58-
"#.to_string()
94+
Ok(html.to_string())
95+
}
5996
}

0 commit comments

Comments
 (0)