diff --git a/src/app.rs b/src/app.rs index abfe75256..1cfc57c2e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -67,6 +67,7 @@ pub async fn start( // Load whitelisted torrents if tracker.is_listed() { tracker + .whitelist_manager .load_whitelist_from_database() .await .expect("Could not load whitelist from database."); diff --git a/src/bootstrap/app.rs b/src/bootstrap/app.rs index 38b7d40c5..788037b0b 100644 --- a/src/bootstrap/app.rs +++ b/src/bootstrap/app.rs @@ -21,7 +21,9 @@ use tracing::instrument; use super::config::initialize_configuration; use crate::bootstrap; -use crate::core::services::tracker_factory; +use crate::core::databases::Database; +use crate::core::services::{initialize_database, initialize_whitelist, tracker_factory}; +use crate::core::whitelist::WhiteListManager; use crate::core::Tracker; use crate::servers::udp::server::banning::BanService; use crate::servers::udp::server::launcher::MAX_CONNECTION_ID_ERRORS_PER_IP; @@ -105,7 +107,17 @@ pub fn initialize_static() { #[must_use] #[instrument(skip(config))] pub fn initialize_tracker(config: &Configuration) -> Tracker { - tracker_factory(config) + let (database, whitelist_manager) = initialize_tracker_dependencies(config); + + tracker_factory(config, &database, &whitelist_manager) +} + +#[must_use] +pub fn initialize_tracker_dependencies(config: &Configuration) -> (Arc>, Arc) { + let database = initialize_database(config); + let whitelist_manager = initialize_whitelist(database.clone()); + + (database, whitelist_manager) } /// It initializes the log threshold, format and channel. diff --git a/src/core/mod.rs b/src/core/mod.rs index 6ba8e94ad..f142fa26e 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -445,6 +445,7 @@ pub mod error; pub mod services; pub mod statistics; pub mod torrent; +pub mod whitelist; pub mod peer_tests; @@ -456,11 +457,9 @@ use std::time::Duration; use auth::PeerKey; use bittorrent_primitives::info_hash::InfoHash; -use databases::driver::Driver; use error::PeerKeyError; use tokio::sync::mpsc::error::SendError; use torrust_tracker_clock::clock::Time; -use torrust_tracker_configuration::v2_0_0::database; use torrust_tracker_configuration::{AnnouncePolicy, Core, TORRENT_PEERS_LIMIT}; use torrust_tracker_located_error::Located; use torrust_tracker_primitives::core::{AnnounceData, ScrapeData}; @@ -470,6 +469,7 @@ use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; use torrust_tracker_torrent_repository::entry::EntrySync; use torrust_tracker_torrent_repository::repository::Repository; use tracing::instrument; +use whitelist::WhiteListManager; use self::auth::Key; use self::error::Error; @@ -498,7 +498,7 @@ pub struct Tracker { keys: tokio::sync::RwLock>, /// The list of allowed torrents. Only for listed trackers. - whitelist: tokio::sync::RwLock>, + pub whitelist_manager: Arc, /// The in-memory torrents repository. torrents: Arc, @@ -574,24 +574,19 @@ impl Tracker { /// Will return a `databases::error::Error` if unable to connect to database. The `Tracker` is responsible for the persistence. pub fn new( config: &Core, + database: &Arc>, + whitelist_manager: &Arc, stats_event_sender: Option>, stats_repository: statistics::repository::Repository, ) -> Result { - let driver = match config.database.driver { - database::Driver::Sqlite3 => Driver::Sqlite3, - database::Driver::MySQL => Driver::MySQL, - }; - - let database = Arc::new(databases::driver::build(&driver, &config.database.path)?); - Ok(Tracker { config: config.clone(), + database: database.clone(), keys: tokio::sync::RwLock::new(std::collections::HashMap::new()), - whitelist: tokio::sync::RwLock::new(std::collections::HashSet::new()), + whitelist_manager: whitelist_manager.clone(), torrents: Arc::default(), stats_event_sender, stats_repository, - database, }) } @@ -1049,7 +1044,7 @@ impl Tracker { return Ok(()); } - if self.is_info_hash_whitelisted(info_hash).await { + if self.whitelist_manager.is_info_hash_whitelisted(info_hash).await { return Ok(()); } @@ -1059,104 +1054,6 @@ impl Tracker { }) } - /// It adds a torrent to the whitelist. - /// Adding torrents is not relevant to public trackers. - /// - /// # Context: Whitelist - /// - /// # Errors - /// - /// Will return a `database::Error` if unable to add the `info_hash` into the whitelist database. - pub async fn add_torrent_to_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { - self.add_torrent_to_database_whitelist(info_hash)?; - self.add_torrent_to_memory_whitelist(info_hash).await; - Ok(()) - } - - /// It adds a torrent to the whitelist if it has not been whitelisted previously - fn add_torrent_to_database_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { - let is_whitelisted = self.database.is_info_hash_whitelisted(*info_hash)?; - - if is_whitelisted { - return Ok(()); - } - - self.database.add_info_hash_to_whitelist(*info_hash)?; - - Ok(()) - } - - pub async fn add_torrent_to_memory_whitelist(&self, info_hash: &InfoHash) -> bool { - self.whitelist.write().await.insert(*info_hash) - } - - /// It removes a torrent from the whitelist. - /// Removing torrents is not relevant to public trackers. - /// - /// # Context: Whitelist - /// - /// # Errors - /// - /// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database. - pub async fn remove_torrent_from_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { - self.remove_torrent_from_database_whitelist(info_hash)?; - self.remove_torrent_from_memory_whitelist(info_hash).await; - Ok(()) - } - - /// It removes a torrent from the whitelist in the database. - /// - /// # Context: Whitelist - /// - /// # Errors - /// - /// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database. - pub fn remove_torrent_from_database_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { - let is_whitelisted = self.database.is_info_hash_whitelisted(*info_hash)?; - - if !is_whitelisted { - return Ok(()); - } - - self.database.remove_info_hash_from_whitelist(*info_hash)?; - - Ok(()) - } - - /// It removes a torrent from the whitelist in memory. - /// - /// # Context: Whitelist - pub async fn remove_torrent_from_memory_whitelist(&self, info_hash: &InfoHash) -> bool { - self.whitelist.write().await.remove(info_hash) - } - - /// It checks if a torrent is whitelisted. - /// - /// # Context: Whitelist - pub async fn is_info_hash_whitelisted(&self, info_hash: &InfoHash) -> bool { - self.whitelist.read().await.contains(info_hash) - } - - /// It loads the whitelist from the database. - /// - /// # Context: Whitelist - /// - /// # Errors - /// - /// Will return a `database::Error` if unable to load the list whitelisted `info_hash`s from the database. - pub async fn load_whitelist_from_database(&self) -> Result<(), databases::error::Error> { - let whitelisted_torrents_from_database = self.database.load_whitelist()?; - let mut whitelist = self.whitelist.write().await; - - whitelist.clear(); - - for info_hash in whitelisted_torrents_from_database { - let _: bool = whitelist.insert(info_hash); - } - - Ok(()) - } - /// It return the `Tracker` [`statistics::metrics::Metrics`]. /// /// # Context: Statistics @@ -1214,26 +1111,37 @@ mod tests { use torrust_tracker_primitives::DurationSinceUnixEpoch; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::peer::Peer; use crate::core::services::tracker_factory; + use crate::core::whitelist::WhiteListManager; use crate::core::{TorrentsMetrics, Tracker}; fn public_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_public()) + let config = configuration::ephemeral_public(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn private_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_private()) + let config = configuration::ephemeral_private(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } - fn whitelisted_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_listed()) + fn whitelisted_tracker() -> (Tracker, Arc) { + let config = configuration::ephemeral_listed(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = tracker_factory(&config, &database, &whitelist_manager); + + (tracker, whitelist_manager) } pub fn tracker_persisting_torrents_in_database() -> Tracker { - let mut configuration = configuration::ephemeral(); - configuration.core.tracker_policy.persistent_torrent_completed_stat = true; - tracker_factory(&configuration) + let mut config = configuration::ephemeral_listed(); + config.core.tracker_policy.persistent_torrent_completed_stat = true; + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn sample_info_hash() -> InfoHash { @@ -1760,11 +1668,11 @@ mod tests { #[tokio::test] async fn it_should_authorize_the_announce_and_scrape_actions_on_whitelisted_torrents() { - let tracker = whitelisted_tracker(); + let (tracker, whitelist_manager) = whitelisted_tracker(); let info_hash = sample_info_hash(); - let result = tracker.add_torrent_to_whitelist(&info_hash).await; + let result = whitelist_manager.add_torrent_to_whitelist(&info_hash).await; assert!(result.is_ok()); let result = tracker.authorize(&info_hash).await; @@ -1773,7 +1681,7 @@ mod tests { #[tokio::test] async fn it_should_not_authorize_the_announce_and_scrape_actions_on_not_whitelisted_torrents() { - let tracker = whitelisted_tracker(); + let (tracker, _whitelist_manager) = whitelisted_tracker(); let info_hash = sample_info_hash(); @@ -1785,28 +1693,33 @@ mod tests { mod handling_the_torrent_whitelist { use crate::core::tests::the_tracker::{sample_info_hash, whitelisted_tracker}; + // todo: after extracting the WhitelistManager from the Tracker, + // there is no need to use the tracker to test the whitelist. + // Test not using the `tracker` (`_tracker` variable) should be + // moved to the whitelist module. + #[tokio::test] async fn it_should_add_a_torrent_to_the_whitelist() { - let tracker = whitelisted_tracker(); + let (_tracker, whitelist_manager) = whitelisted_tracker(); let info_hash = sample_info_hash(); - tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); - assert!(tracker.is_info_hash_whitelisted(&info_hash).await); + assert!(whitelist_manager.is_info_hash_whitelisted(&info_hash).await); } #[tokio::test] async fn it_should_remove_a_torrent_from_the_whitelist() { - let tracker = whitelisted_tracker(); + let (_tracker, whitelist_manager) = whitelisted_tracker(); let info_hash = sample_info_hash(); - tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); - tracker.remove_torrent_from_whitelist(&info_hash).await.unwrap(); + whitelist_manager.remove_torrent_from_whitelist(&info_hash).await.unwrap(); - assert!(!tracker.is_info_hash_whitelisted(&info_hash).await); + assert!(!whitelist_manager.is_info_hash_whitelisted(&info_hash).await); } mod persistence { @@ -1814,19 +1727,19 @@ mod tests { #[tokio::test] async fn it_should_load_the_whitelist_from_the_database() { - let tracker = whitelisted_tracker(); + let (_tracker, whitelist_manager) = whitelisted_tracker(); let info_hash = sample_info_hash(); - tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); + + whitelist_manager.remove_torrent_from_memory_whitelist(&info_hash).await; - // Remove torrent from the in-memory whitelist - tracker.whitelist.write().await.remove(&info_hash); - assert!(!tracker.is_info_hash_whitelisted(&info_hash).await); + assert!(!whitelist_manager.is_info_hash_whitelisted(&info_hash).await); - tracker.load_whitelist_from_database().await.unwrap(); + whitelist_manager.load_whitelist_from_database().await.unwrap(); - assert!(tracker.is_info_hash_whitelisted(&info_hash).await); + assert!(whitelist_manager.is_info_hash_whitelisted(&info_hash).await); } } } @@ -1857,7 +1770,7 @@ mod tests { #[tokio::test] async fn it_should_return_the_zeroed_swarm_metadata_for_the_requested_file_if_it_is_not_whitelisted() { - let tracker = whitelisted_tracker(); + let (tracker, _whitelist_manager) = whitelisted_tracker(); let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::().unwrap(); diff --git a/src/core/services/mod.rs b/src/core/services/mod.rs index 166f40df4..a6b5e3371 100644 --- a/src/core/services/mod.rs +++ b/src/core/services/mod.rs @@ -9,8 +9,13 @@ pub mod torrent; use std::sync::Arc; +use databases::driver::Driver; +use torrust_tracker_configuration::v2_0_0::database; use torrust_tracker_configuration::Configuration; +use super::databases::{self, Database}; +use super::whitelist::persisted::DatabaseWhitelist; +use super::whitelist::WhiteListManager; use crate::core::Tracker; /// It returns a new tracker building its dependencies. @@ -19,15 +24,42 @@ use crate::core::Tracker; /// /// Will panic if tracker cannot be instantiated. #[must_use] -pub fn tracker_factory(config: &Configuration) -> Tracker { - // Initialize statistics +pub fn tracker_factory( + config: &Configuration, + database: &Arc>, + whitelist_manager: &Arc, +) -> Tracker { let (stats_event_sender, stats_repository) = statistics::setup::factory(config.core.tracker_usage_statistics); - // Initialize Torrust tracker - match Tracker::new(&Arc::new(config).core, stats_event_sender, stats_repository) { + match Tracker::new( + &Arc::new(config).core, + database, + whitelist_manager, + stats_event_sender, + stats_repository, + ) { Ok(tracker) => tracker, Err(error) => { panic!("{}", error) } } } + +/// # Panics +/// +/// Will panic if database cannot be initialized. +#[must_use] +pub fn initialize_database(config: &Configuration) -> Arc> { + let driver = match config.core.database.driver { + database::Driver::Sqlite3 => Driver::Sqlite3, + database::Driver::MySQL => Driver::MySQL, + }; + + Arc::new(databases::driver::build(&driver, &config.core.database.path).expect("Database driver build failed.")) +} + +#[must_use] +pub fn initialize_whitelist(database: Arc>) -> Arc { + let database_whitelist = Arc::new(DatabaseWhitelist::new(database)); + Arc::new(WhiteListManager::new(database_whitelist)) +} diff --git a/src/core/services/statistics/mod.rs b/src/core/services/statistics/mod.rs index 4143aaf1f..d4e77ce4c 100644 --- a/src/core/services/statistics/mod.rs +++ b/src/core/services/statistics/mod.rs @@ -112,6 +112,7 @@ mod tests { use torrust_tracker_primitives::torrent_metrics::TorrentsMetrics; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core; use crate::core::services::statistics::{get_metrics, TrackerMetrics}; use crate::core::services::tracker_factory; @@ -124,7 +125,9 @@ mod tests { #[tokio::test] async fn the_statistics_service_should_return_the_tracker_metrics() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&tracker_configuration(), &database, &whitelist_manager)); let ban_service = Arc::new(RwLock::new(BanService::new(MAX_CONNECTION_ID_ERRORS_PER_IP))); let tracker_metrics = get_metrics(tracker.clone(), ban_service.clone()).await; diff --git a/src/core/services/torrent.rs b/src/core/services/torrent.rs index e63d2efa2..1be2acc93 100644 --- a/src/core/services/torrent.rs +++ b/src/core/services/torrent.rs @@ -129,6 +129,7 @@ mod tests { use torrust_tracker_configuration::Configuration; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::services::torrent::tests::sample_peer; use crate::core::services::torrent::{get_torrent_info, Info}; use crate::core::services::tracker_factory; @@ -139,7 +140,9 @@ mod tests { #[tokio::test] async fn should_return_none_if_the_tracker_does_not_have_the_torrent() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&config, &database, &whitelist_manager)); let torrent_info = get_torrent_info( tracker.clone(), @@ -152,7 +155,9 @@ mod tests { #[tokio::test] async fn should_return_the_torrent_info_if_the_tracker_has_the_torrent() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&config, &database, &whitelist_manager)); let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); @@ -182,6 +187,7 @@ mod tests { use torrust_tracker_configuration::Configuration; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::services::torrent::tests::sample_peer; use crate::core::services::torrent::{get_torrents_page, BasicInfo, Pagination}; use crate::core::services::tracker_factory; @@ -192,7 +198,9 @@ mod tests { #[tokio::test] async fn should_return_an_empty_result_if_the_tracker_does_not_have_any_torrent() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&config, &database, &whitelist_manager)); let torrents = get_torrents_page(tracker.clone(), Some(&Pagination::default())).await; @@ -201,7 +209,9 @@ mod tests { #[tokio::test] async fn should_return_a_summarized_info_for_all_torrents() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&config, &database, &whitelist_manager)); let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); @@ -223,7 +233,9 @@ mod tests { #[tokio::test] async fn should_allow_limiting_the_number_of_torrents_in_the_result() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&config, &database, &whitelist_manager)); let hash1 = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash1 = InfoHash::from_str(&hash1).unwrap(); @@ -243,7 +255,9 @@ mod tests { #[tokio::test] async fn should_allow_using_pagination_in_the_result() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&config, &database, &whitelist_manager)); let hash1 = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash1 = InfoHash::from_str(&hash1).unwrap(); @@ -272,7 +286,9 @@ mod tests { #[tokio::test] async fn should_return_torrents_ordered_by_info_hash() { - let tracker = Arc::new(tracker_factory(&tracker_configuration())); + let config = tracker_configuration(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + let tracker = Arc::new(tracker_factory(&config, &database, &whitelist_manager)); let hash1 = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash1 = InfoHash::from_str(&hash1).unwrap(); diff --git a/src/core/whitelist/in_memory.rs b/src/core/whitelist/in_memory.rs new file mode 100644 index 000000000..78e0eb11f --- /dev/null +++ b/src/core/whitelist/in_memory.rs @@ -0,0 +1,88 @@ +use bittorrent_primitives::info_hash::InfoHash; + +/// The in-memory list of allowed torrents. +#[derive(Debug, Default)] +pub struct InMemoryWhitelist { + /// The list of allowed torrents. + whitelist: tokio::sync::RwLock>, +} + +impl InMemoryWhitelist { + /// It adds a torrent from the whitelist in memory. + pub async fn add(&self, info_hash: &InfoHash) -> bool { + self.whitelist.write().await.insert(*info_hash) + } + + /// It removes a torrent from the whitelist in memory. + pub async fn remove(&self, info_hash: &InfoHash) -> bool { + self.whitelist.write().await.remove(info_hash) + } + + /// It checks if it contains an info-hash. + pub async fn contains(&self, info_hash: &InfoHash) -> bool { + self.whitelist.read().await.contains(info_hash) + } + + /// It clears the whitelist. + pub async fn clear(&self) { + let mut whitelist = self.whitelist.write().await; + whitelist.clear(); + } +} + +#[cfg(test)] +mod tests { + use bittorrent_primitives::info_hash::InfoHash; + + use crate::core::whitelist::in_memory::InMemoryWhitelist; + + fn sample_info_hash() -> InfoHash { + "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::().unwrap() // # DevSkim: ignore DS173237 + } + + #[tokio::test] + async fn should_allow_adding_a_new_torrent_to_the_whitelist() { + let info_hash = sample_info_hash(); + + let whitelist = InMemoryWhitelist::default(); + + whitelist.add(&info_hash).await; + + assert!(whitelist.contains(&info_hash).await); + } + + #[tokio::test] + async fn should_allow_removing_a_new_torrent_to_the_whitelist() { + let info_hash = sample_info_hash(); + + let whitelist = InMemoryWhitelist::default(); + + whitelist.add(&info_hash).await; + whitelist.remove(&sample_info_hash()).await; + + assert!(!whitelist.contains(&info_hash).await); + } + + #[tokio::test] + async fn should_allow_clearing_the_whitelist() { + let info_hash = sample_info_hash(); + + let whitelist = InMemoryWhitelist::default(); + + whitelist.add(&info_hash).await; + whitelist.clear().await; + + assert!(!whitelist.contains(&info_hash).await); + } + + #[tokio::test] + async fn should_allow_checking_if_an_infohash_is_whitelisted() { + let info_hash = sample_info_hash(); + + let whitelist = InMemoryWhitelist::default(); + + whitelist.add(&info_hash).await; + + assert!(whitelist.contains(&info_hash).await); + } +} diff --git a/src/core/whitelist/mod.rs b/src/core/whitelist/mod.rs new file mode 100644 index 000000000..3a88b404c --- /dev/null +++ b/src/core/whitelist/mod.rs @@ -0,0 +1,94 @@ +pub mod in_memory; +pub mod persisted; + +use std::sync::Arc; + +use bittorrent_primitives::info_hash::InfoHash; +use in_memory::InMemoryWhitelist; +use persisted::DatabaseWhitelist; + +use super::databases::{self}; + +/// It handles the list of allowed torrents. Only for listed trackers. +pub struct WhiteListManager { + /// The in-memory list of allowed torrents. + in_memory_whitelist: InMemoryWhitelist, + + /// The persisted list of allowed torrents. + database_whitelist: Arc, +} + +impl WhiteListManager { + #[must_use] + pub fn new(database_whitelist: Arc) -> Self { + Self { + in_memory_whitelist: InMemoryWhitelist::default(), + database_whitelist, + } + } + + /// It adds a torrent to the whitelist. + /// Adding torrents is not relevant to public trackers. + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to add the `info_hash` into the whitelist database. + pub async fn add_torrent_to_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { + self.database_whitelist.add(info_hash)?; + self.in_memory_whitelist.add(info_hash).await; + Ok(()) + } + + /// It removes a torrent from the whitelist. + /// Removing torrents is not relevant to public trackers. + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database. + pub async fn remove_torrent_from_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { + self.database_whitelist.remove(info_hash)?; + self.in_memory_whitelist.remove(info_hash).await; + Ok(()) + } + + /// It removes a torrent from the whitelist in the database. + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database. + pub fn remove_torrent_from_database_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { + self.database_whitelist.remove(info_hash) + } + + /// It adds a torrent from the whitelist in memory. + pub async fn add_torrent_to_memory_whitelist(&self, info_hash: &InfoHash) -> bool { + self.in_memory_whitelist.add(info_hash).await + } + + /// It removes a torrent from the whitelist in memory. + pub async fn remove_torrent_from_memory_whitelist(&self, info_hash: &InfoHash) -> bool { + self.in_memory_whitelist.remove(info_hash).await + } + + /// It checks if a torrent is whitelisted. + pub async fn is_info_hash_whitelisted(&self, info_hash: &InfoHash) -> bool { + self.in_memory_whitelist.contains(info_hash).await + } + + /// It loads the whitelist from the database. + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to load the list whitelisted `info_hash`s from the database. + pub async fn load_whitelist_from_database(&self) -> Result<(), databases::error::Error> { + let whitelisted_torrents_from_database = self.database_whitelist.load_from_database()?; + + self.in_memory_whitelist.clear().await; + + for info_hash in whitelisted_torrents_from_database { + let _: bool = self.in_memory_whitelist.add(&info_hash).await; + } + + Ok(()) + } +} diff --git a/src/core/whitelist/persisted.rs b/src/core/whitelist/persisted.rs new file mode 100644 index 000000000..993060139 --- /dev/null +++ b/src/core/whitelist/persisted.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use bittorrent_primitives::info_hash::InfoHash; + +use super::databases::{self, Database}; + +/// The persisted list of allowed torrents. +pub struct DatabaseWhitelist { + /// A database driver implementation: [`Sqlite3`](crate::core::databases::sqlite) + /// or [`MySQL`](crate::core::databases::mysql) + database: Arc>, +} + +impl DatabaseWhitelist { + #[must_use] + pub fn new(database: Arc>) -> Self { + Self { database } + } + + /// It adds a torrent to the whitelist if it has not been whitelisted previously + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to add the `info_hash` to the whitelist database. + pub fn add(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { + let is_whitelisted = self.database.is_info_hash_whitelisted(*info_hash)?; + + if is_whitelisted { + return Ok(()); + } + + self.database.add_info_hash_to_whitelist(*info_hash)?; + + Ok(()) + } + + /// It removes a torrent from the whitelist in the database. + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database. + pub fn remove(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> { + let is_whitelisted = self.database.is_info_hash_whitelisted(*info_hash)?; + + if !is_whitelisted { + return Ok(()); + } + + self.database.remove_info_hash_from_whitelist(*info_hash)?; + + Ok(()) + } + + /// It loads the whitelist from the database. + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to load the list whitelisted `info_hash`s from the database. + pub fn load_from_database(&self) -> Result, databases::error::Error> { + self.database.load_whitelist() + } +} diff --git a/src/servers/apis/v1/context/whitelist/handlers.rs b/src/servers/apis/v1/context/whitelist/handlers.rs index 04085f8ab..f548f5dc4 100644 --- a/src/servers/apis/v1/context/whitelist/handlers.rs +++ b/src/servers/apis/v1/context/whitelist/handlers.rs @@ -10,7 +10,7 @@ use bittorrent_primitives::info_hash::InfoHash; use super::responses::{ failed_to_reload_whitelist_response, failed_to_remove_torrent_from_whitelist_response, failed_to_whitelist_torrent_response, }; -use crate::core::Tracker; +use crate::core::whitelist::WhiteListManager; use crate::servers::apis::v1::responses::{invalid_info_hash_param_response, ok_response}; use crate::servers::apis::InfoHashParam; @@ -24,12 +24,12 @@ use crate::servers::apis::InfoHashParam; /// Refer to the [API endpoint documentation](crate::servers::apis::v1::context::whitelist#add-a-torrent-to-the-whitelist) /// for more information about this endpoint. pub async fn add_torrent_to_whitelist_handler( - State(tracker): State>, + State(whitelist_manager): State>, Path(info_hash): Path, ) -> Response { match InfoHash::from_str(&info_hash.0) { Err(_) => invalid_info_hash_param_response(&info_hash.0), - Ok(info_hash) => match tracker.add_torrent_to_whitelist(&info_hash).await { + Ok(info_hash) => match whitelist_manager.add_torrent_to_whitelist(&info_hash).await { Ok(()) => ok_response(), Err(e) => failed_to_whitelist_torrent_response(e), }, @@ -47,12 +47,12 @@ pub async fn add_torrent_to_whitelist_handler( /// Refer to the [API endpoint documentation](crate::servers::apis::v1::context::whitelist#remove-a-torrent-from-the-whitelist) /// for more information about this endpoint. pub async fn remove_torrent_from_whitelist_handler( - State(tracker): State>, + State(whitelist_manager): State>, Path(info_hash): Path, ) -> Response { match InfoHash::from_str(&info_hash.0) { Err(_) => invalid_info_hash_param_response(&info_hash.0), - Ok(info_hash) => match tracker.remove_torrent_from_whitelist(&info_hash).await { + Ok(info_hash) => match whitelist_manager.remove_torrent_from_whitelist(&info_hash).await { Ok(()) => ok_response(), Err(e) => failed_to_remove_torrent_from_whitelist_response(e), }, @@ -69,8 +69,8 @@ pub async fn remove_torrent_from_whitelist_handler( /// /// Refer to the [API endpoint documentation](crate::servers::apis::v1::context::whitelist#reload-the-whitelist) /// for more information about this endpoint. -pub async fn reload_whitelist_handler(State(tracker): State>) -> Response { - match tracker.load_whitelist_from_database().await { +pub async fn reload_whitelist_handler(State(whitelist_manager): State>) -> Response { + match whitelist_manager.load_whitelist_from_database().await { Ok(()) => ok_response(), Err(e) => failed_to_reload_whitelist_response(e), } diff --git a/src/servers/apis/v1/context/whitelist/routes.rs b/src/servers/apis/v1/context/whitelist/routes.rs index 35312ea97..c58aa7177 100644 --- a/src/servers/apis/v1/context/whitelist/routes.rs +++ b/src/servers/apis/v1/context/whitelist/routes.rs @@ -14,19 +14,22 @@ use super::handlers::{add_torrent_to_whitelist_handler, reload_whitelist_handler use crate::core::Tracker; /// It adds the routes to the router for the [`whitelist`](crate::servers::apis::v1::context::whitelist) API context. -pub fn add(prefix: &str, router: Router, tracker: Arc) -> Router { +pub fn add(prefix: &str, router: Router, tracker: &Arc) -> Router { let prefix = format!("{prefix}/whitelist"); router // Whitelisted torrents .route( &format!("{prefix}/{{info_hash}}"), - post(add_torrent_to_whitelist_handler).with_state(tracker.clone()), + post(add_torrent_to_whitelist_handler).with_state(tracker.whitelist_manager.clone()), ) .route( &format!("{prefix}/{{info_hash}}"), - delete(remove_torrent_from_whitelist_handler).with_state(tracker.clone()), + delete(remove_torrent_from_whitelist_handler).with_state(tracker.whitelist_manager.clone()), ) // Whitelist commands - .route(&format!("{prefix}/reload"), get(reload_whitelist_handler).with_state(tracker)) + .route( + &format!("{prefix}/reload"), + get(reload_whitelist_handler).with_state(tracker.whitelist_manager.clone()), + ) } diff --git a/src/servers/apis/v1/routes.rs b/src/servers/apis/v1/routes.rs index 23ef6c47e..4c97c7578 100644 --- a/src/servers/apis/v1/routes.rs +++ b/src/servers/apis/v1/routes.rs @@ -14,7 +14,7 @@ pub fn add(prefix: &str, router: Router, tracker: Arc, ban_service: Arc let router = auth_key::routes::add(&v1_prefix, router, tracker.clone()); let router = stats::routes::add(&v1_prefix, router, tracker.clone(), ban_service); - let router = whitelist::routes::add(&v1_prefix, router, tracker.clone()); + let router = whitelist::routes::add(&v1_prefix, router, &tracker); torrent::routes::add(&v1_prefix, router, tracker) } diff --git a/src/servers/http/v1/handlers/announce.rs b/src/servers/http/v1/handlers/announce.rs index a17e877fa..df4658420 100644 --- a/src/servers/http/v1/handlers/announce.rs +++ b/src/servers/http/v1/handlers/announce.rs @@ -185,23 +185,32 @@ mod tests { use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::services::tracker_factory; use crate::core::Tracker; fn private_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_private()) + let config = configuration::ephemeral_private(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn whitelisted_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_listed()) + let config = configuration::ephemeral_listed(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn tracker_on_reverse_proxy() -> Tracker { - tracker_factory(&configuration::ephemeral_with_reverse_proxy()) + let config = configuration::ephemeral_with_reverse_proxy(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn tracker_not_on_reverse_proxy() -> Tracker { - tracker_factory(&configuration::ephemeral_without_reverse_proxy()) + let config = configuration::ephemeral_without_reverse_proxy(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn sample_announce_request() -> Announce { diff --git a/src/servers/http/v1/handlers/scrape.rs b/src/servers/http/v1/handlers/scrape.rs index 2aa1bd9f8..dd144d898 100644 --- a/src/servers/http/v1/handlers/scrape.rs +++ b/src/servers/http/v1/handlers/scrape.rs @@ -121,23 +121,32 @@ mod tests { use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::services::tracker_factory; use crate::core::Tracker; fn private_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_private()) + let config = configuration::ephemeral_private(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn whitelisted_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_listed()) + let config = configuration::ephemeral_listed(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn tracker_on_reverse_proxy() -> Tracker { - tracker_factory(&configuration::ephemeral_with_reverse_proxy()) + let config = configuration::ephemeral_with_reverse_proxy(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn tracker_not_on_reverse_proxy() -> Tracker { - tracker_factory(&configuration::ephemeral_without_reverse_proxy()) + let config = configuration::ephemeral_without_reverse_proxy(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn sample_scrape_request() -> Scrape { diff --git a/src/servers/http/v1/services/announce.rs b/src/servers/http/v1/services/announce.rs index df827aee2..f19c69c2f 100644 --- a/src/servers/http/v1/services/announce.rs +++ b/src/servers/http/v1/services/announce.rs @@ -59,11 +59,14 @@ mod tests { use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::services::tracker_factory; use crate::core::Tracker; fn public_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_public()) + let config = configuration::ephemeral_public(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn sample_info_hash() -> InfoHash { @@ -107,10 +110,26 @@ mod tests { use torrust_tracker_test_helpers::configuration; use super::{sample_peer_using_ipv4, sample_peer_using_ipv6}; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::{statistics, PeersWanted, Tracker}; use crate::servers::http::v1::services::announce::invoke; use crate::servers::http::v1::services::announce::tests::{public_tracker, sample_info_hash, sample_peer}; + fn test_tracker_factory(stats_event_sender: Option>) -> Tracker { + let config = configuration::ephemeral(); + + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + + Tracker::new( + &config.core, + &database, + &whitelist_manager, + stats_event_sender, + statistics::repository::Repository::new(), + ) + .unwrap() + } + #[tokio::test] async fn it_should_return_the_announce_data() { let tracker = Arc::new(public_tracker()); @@ -142,14 +161,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - &configuration::ephemeral().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); let mut peer = sample_peer_using_ipv4(); @@ -162,12 +174,7 @@ mod tests { 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, ))); - Tracker::new( - &configuration.core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap() + test_tracker_factory(Some(stats_event_sender)) } fn peer_with_the_ipv4_loopback_ip() -> peer::Peer { @@ -213,14 +220,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - &configuration::ephemeral().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); let mut peer = sample_peer_using_ipv6(); diff --git a/src/servers/http/v1/services/scrape.rs b/src/servers/http/v1/services/scrape.rs index 80d81d78a..0a96031a0 100644 --- a/src/servers/http/v1/services/scrape.rs +++ b/src/servers/http/v1/services/scrape.rs @@ -67,11 +67,14 @@ mod tests { use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; use torrust_tracker_test_helpers::configuration; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::services::tracker_factory; - use crate::core::Tracker; + use crate::core::{statistics, Tracker}; fn public_tracker() -> Tracker { - tracker_factory(&configuration::ephemeral_public()) + let config = configuration::ephemeral_public(); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + tracker_factory(&config, &database, &whitelist_manager) } fn sample_info_hashes() -> Vec { @@ -94,6 +97,21 @@ mod tests { } } + fn test_tracker_factory(stats_event_sender: Option>) -> Tracker { + let config = configuration::ephemeral(); + + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + + Tracker::new( + &config.core, + &database, + &whitelist_manager, + stats_event_sender, + statistics::repository::Repository::new(), + ) + .unwrap() + } + mod with_real_data { use std::future; @@ -103,12 +121,11 @@ mod tests { use mockall::predicate::eq; use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; - use torrust_tracker_test_helpers::configuration; - use crate::core::{statistics, PeersWanted, Tracker}; + use crate::core::{statistics, PeersWanted}; use crate::servers::http::v1::services::scrape::invoke; use crate::servers::http::v1::services::scrape::tests::{ - public_tracker, sample_info_hash, sample_info_hashes, sample_peer, + public_tracker, sample_info_hash, sample_info_hashes, sample_peer, test_tracker_factory, }; #[tokio::test] @@ -148,14 +165,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - &configuration::ephemeral().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)); @@ -172,14 +182,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - &configuration::ephemeral().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)); @@ -195,12 +198,11 @@ mod tests { use mockall::predicate::eq; use torrust_tracker_primitives::core::ScrapeData; - use torrust_tracker_test_helpers::configuration; - use crate::core::{statistics, PeersWanted, Tracker}; + use crate::core::{statistics, PeersWanted}; use crate::servers::http::v1::services::scrape::fake; use crate::servers::http::v1::services::scrape::tests::{ - public_tracker, sample_info_hash, sample_info_hashes, sample_peer, + public_tracker, sample_info_hash, sample_info_hashes, sample_peer, test_tracker_factory, }; #[tokio::test] @@ -232,14 +234,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - &configuration::ephemeral().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)); @@ -256,14 +251,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - &configuration::ephemeral().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)); diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index 1a9c164e2..292ccfd3a 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -435,8 +435,9 @@ mod tests { use torrust_tracker_test_helpers::configuration; use super::gen_remote_fingerprint; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core::services::tracker_factory; - use crate::core::Tracker; + use crate::core::{statistics, Tracker}; use crate::CurrentClock; fn tracker_configuration() -> Configuration { @@ -455,8 +456,9 @@ mod tests { initialized_tracker(&configuration::ephemeral_listed()) } - fn initialized_tracker(configuration: &Configuration) -> Arc { - tracker_factory(configuration).into() + fn initialized_tracker(config: &Configuration) -> Arc { + let (database, whitelist_manager) = initialize_tracker_dependencies(config); + tracker_factory(config, &database, &whitelist_manager).into() } fn sample_ipv4_remote_addr() -> SocketAddr { @@ -553,6 +555,21 @@ mod tests { } } + fn test_tracker_factory(stats_event_sender: Option>) -> Tracker { + let config = tracker_configuration(); + + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); + + Tracker::new( + &config.core, + &database, + &whitelist_manager, + stats_event_sender, + statistics::repository::Repository::new(), + ) + .unwrap() + } + mod connect_request { use std::future; @@ -561,13 +578,13 @@ mod tests { use aquatic_udp_protocol::{ConnectRequest, ConnectResponse, Response, TransactionId}; use mockall::predicate::eq; - use super::{sample_ipv4_socket_address, sample_ipv6_remote_addr, tracker_configuration}; - use crate::core::{self, statistics}; + use super::{sample_ipv4_socket_address, sample_ipv6_remote_addr}; + use crate::core::statistics; use crate::servers::udp::connection_cookie::make; use crate::servers::udp::handlers::handle_connect; use crate::servers::udp::handlers::tests::{ public_tracker, sample_ipv4_remote_addr, sample_ipv4_remote_addr_fingerprint, sample_ipv6_remote_addr_fingerprint, - sample_issue_time, + sample_issue_time, test_tracker_factory, }; fn sample_connect_request() -> ConnectRequest { @@ -639,14 +656,7 @@ mod tests { let client_socket_address = sample_ipv4_socket_address(); - let torrent_tracker = Arc::new( - core::Tracker::new( - &tracker_configuration().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let torrent_tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); handle_connect( client_socket_address, &sample_connect_request(), @@ -666,14 +676,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let torrent_tracker = Arc::new( - core::Tracker::new( - &tracker_configuration().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let torrent_tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); handle_connect( sample_ipv6_remote_addr(), &sample_connect_request(), @@ -774,7 +777,7 @@ mod tests { use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder; use crate::servers::udp::handlers::tests::{ gen_remote_fingerprint, public_tracker, sample_cookie_valid_range, sample_ipv4_socket_address, sample_issue_time, - tracker_configuration, TorrentPeerBuilder, + test_tracker_factory, TorrentPeerBuilder, }; use crate::servers::udp::handlers::{handle_announce, AnnounceResponseFixedData}; @@ -927,14 +930,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - core::Tracker::new( - &tracker_configuration().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); handle_announce( sample_ipv4_socket_address(), @@ -1013,7 +1009,7 @@ mod tests { use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder; use crate::servers::udp::handlers::tests::{ gen_remote_fingerprint, public_tracker, sample_cookie_valid_range, sample_ipv6_remote_addr, sample_issue_time, - tracker_configuration, TorrentPeerBuilder, + test_tracker_factory, TorrentPeerBuilder, }; use crate::servers::udp::handlers::{handle_announce, AnnounceResponseFixedData}; @@ -1173,14 +1169,7 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - core::Tracker::new( - &tracker_configuration().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); let remote_addr = sample_ipv6_remote_addr(); @@ -1199,6 +1188,7 @@ mod tests { use aquatic_udp_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId}; + use crate::bootstrap::app::initialize_tracker_dependencies; use crate::core; use crate::core::statistics::keeper::Keeper; use crate::servers::udp::connection_cookie::make; @@ -1210,10 +1200,20 @@ mod tests { #[tokio::test] async fn the_peer_ip_should_be_changed_to_the_external_ip_in_the_tracker_configuration() { - let configuration = Arc::new(TrackerConfigurationBuilder::default().with_external_ip("::126.0.0.1").into()); + let config = Arc::new(TrackerConfigurationBuilder::default().with_external_ip("::126.0.0.1").into()); + let (database, whitelist_manager) = initialize_tracker_dependencies(&config); let (stats_event_sender, stats_repository) = Keeper::new_active_instance(); - let tracker = - Arc::new(core::Tracker::new(&configuration.core, Some(stats_event_sender), stats_repository).unwrap()); + + let tracker = Arc::new( + core::Tracker::new( + &config.core, + &database, + &whitelist_manager, + Some(stats_event_sender), + stats_repository, + ) + .unwrap(), + ); let loopback_ipv4 = Ipv4Addr::new(127, 0, 0, 1); let loopback_ipv6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); @@ -1391,7 +1391,10 @@ mod tests { add_a_seeder(tracker.clone(), &remote_addr, &info_hash).await; - tracker.add_torrent_to_memory_whitelist(&info_hash.0.into()).await; + tracker + .whitelist_manager + .add_torrent_to_memory_whitelist(&info_hash.0.into()) + .await; let request = build_scrape_request(&remote_addr, &info_hash); @@ -1453,10 +1456,10 @@ mod tests { use mockall::predicate::eq; use super::sample_scrape_request; - use crate::core::{self, statistics}; + use crate::core::statistics; use crate::servers::udp::handlers::handle_scrape; use crate::servers::udp::handlers::tests::{ - sample_cookie_valid_range, sample_ipv4_remote_addr, tracker_configuration, + sample_cookie_valid_range, sample_ipv4_remote_addr, test_tracker_factory, }; #[tokio::test] @@ -1470,14 +1473,7 @@ mod tests { let stats_event_sender = Box::new(stats_event_sender_mock); let remote_addr = sample_ipv4_remote_addr(); - let tracker = Arc::new( - core::Tracker::new( - &tracker_configuration().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); handle_scrape( remote_addr, @@ -1497,10 +1493,10 @@ mod tests { use mockall::predicate::eq; use super::sample_scrape_request; - use crate::core::{self, statistics}; + use crate::core::statistics; use crate::servers::udp::handlers::handle_scrape; use crate::servers::udp::handlers::tests::{ - sample_cookie_valid_range, sample_ipv6_remote_addr, tracker_configuration, + sample_cookie_valid_range, sample_ipv6_remote_addr, test_tracker_factory, }; #[tokio::test] @@ -1514,14 +1510,7 @@ mod tests { let stats_event_sender = Box::new(stats_event_sender_mock); let remote_addr = sample_ipv6_remote_addr(); - let tracker = Arc::new( - core::Tracker::new( - &tracker_configuration().core, - Some(stats_event_sender), - statistics::repository::Repository::new(), - ) - .unwrap(), - ); + let tracker = Arc::new(test_tracker_factory(Some(stats_event_sender))); handle_scrape( remote_addr, diff --git a/tests/servers/api/environment.rs b/tests/servers/api/environment.rs index 70f2d4c65..37d031e1c 100644 --- a/tests/servers/api/environment.rs +++ b/tests/servers/api/environment.rs @@ -8,6 +8,7 @@ use torrust_tracker_api_client::connection_info::{ConnectionInfo, Origin}; use torrust_tracker_configuration::{Configuration, HttpApi}; use torrust_tracker_lib::bootstrap::app::initialize_with_configuration; use torrust_tracker_lib::bootstrap::jobs::make_rust_tls; +use torrust_tracker_lib::core::whitelist::WhiteListManager; use torrust_tracker_lib::core::Tracker; use torrust_tracker_lib::servers::apis::server::{ApiServer, Launcher, Running, Stopped}; use torrust_tracker_lib::servers::registar::Registar; @@ -21,6 +22,7 @@ where { pub config: Arc, pub tracker: Arc, + pub whitelist_manager: Arc, pub ban_service: Arc>, pub registar: Registar, pub server: ApiServer, @@ -40,6 +42,9 @@ impl Environment { pub fn new(configuration: &Arc) -> Self { let tracker = initialize_with_configuration(configuration); + // todo: get from `initialize_with_configuration` + let whitelist_manager = tracker.whitelist_manager.clone(); + let ban_service = Arc::new(RwLock::new(BanService::new(MAX_CONNECTION_ID_ERRORS_PER_IP))); let config = Arc::new(configuration.http_api.clone().expect("missing API configuration")); @@ -53,6 +58,7 @@ impl Environment { Self { config, tracker, + whitelist_manager, ban_service, registar: Registar::default(), server, @@ -65,6 +71,7 @@ impl Environment { Environment { config: self.config, tracker: self.tracker.clone(), + whitelist_manager: self.whitelist_manager.clone(), ban_service: self.ban_service.clone(), registar: self.registar.clone(), server: self @@ -85,6 +92,7 @@ impl Environment { Environment { config: self.config, tracker: self.tracker, + whitelist_manager: self.whitelist_manager, ban_service: self.ban_service, registar: Registar::default(), server: self.server.stop().await.unwrap(), diff --git a/tests/servers/api/v1/contract/context/whitelist.rs b/tests/servers/api/v1/contract/context/whitelist.rs index 6dde663a5..aef1db4f1 100644 --- a/tests/servers/api/v1/contract/context/whitelist.rs +++ b/tests/servers/api/v1/contract/context/whitelist.rs @@ -31,7 +31,7 @@ async fn should_allow_whitelisting_a_torrent() { assert_ok(response).await; assert!( - env.tracker + env.whitelist_manager .is_info_hash_whitelisted(&InfoHash::from_str(&info_hash).unwrap()) .await ); @@ -167,7 +167,7 @@ async fn should_allow_removing_a_torrent_from_the_whitelist() { let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); - env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + env.whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); let request_id = Uuid::new_v4(); @@ -176,7 +176,7 @@ async fn should_allow_removing_a_torrent_from_the_whitelist() { .await; assert_ok(response).await; - assert!(!env.tracker.is_info_hash_whitelisted(&info_hash).await); + assert!(!env.whitelist_manager.is_info_hash_whitelisted(&info_hash).await); env.stop().await; } @@ -237,7 +237,7 @@ async fn should_fail_when_the_torrent_cannot_be_removed_from_the_whitelist() { let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); - env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + env.whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); force_database_error(&env.tracker); @@ -266,7 +266,7 @@ async fn should_not_allow_removing_a_torrent_from_the_whitelist_for_unauthentica let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); - env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + env.whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); let request_id = Uuid::new_v4(); @@ -281,7 +281,7 @@ async fn should_not_allow_removing_a_torrent_from_the_whitelist_for_unauthentica "Expected logs to contain: ERROR ... API ... request_id={request_id}" ); - env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + env.whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); let request_id = Uuid::new_v4(); @@ -307,7 +307,7 @@ async fn should_allow_reload_the_whitelist_from_the_database() { let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); - env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + env.whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); let request_id = Uuid::new_v4(); @@ -338,7 +338,7 @@ async fn should_fail_when_the_whitelist_cannot_be_reloaded_from_the_database() { let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); - env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + env.whitelist_manager.add_torrent_to_whitelist(&info_hash).await.unwrap(); force_database_error(&env.tracker); diff --git a/tests/servers/http/environment.rs b/tests/servers/http/environment.rs index d615d7eaf..6d4001e6c 100644 --- a/tests/servers/http/environment.rs +++ b/tests/servers/http/environment.rs @@ -5,6 +5,7 @@ use futures::executor::block_on; use torrust_tracker_configuration::{Configuration, HttpTracker}; use torrust_tracker_lib::bootstrap::app::initialize_with_configuration; use torrust_tracker_lib::bootstrap::jobs::make_rust_tls; +use torrust_tracker_lib::core::whitelist::WhiteListManager; use torrust_tracker_lib::core::Tracker; use torrust_tracker_lib::servers::http::server::{HttpServer, Launcher, Running, Stopped}; use torrust_tracker_lib::servers::registar::Registar; @@ -13,6 +14,7 @@ use torrust_tracker_primitives::peer; pub struct Environment { pub config: Arc, pub tracker: Arc, + pub whitelist_manager: Arc, pub registar: Registar, pub server: HttpServer, } @@ -29,6 +31,8 @@ impl Environment { pub fn new(configuration: &Arc) -> Self { let tracker = initialize_with_configuration(configuration); + let whitelist_manager = tracker.whitelist_manager.clone(); + let http_tracker = configuration .http_trackers .clone() @@ -45,6 +49,7 @@ impl Environment { Self { config, tracker, + whitelist_manager, registar: Registar::default(), server, } @@ -55,6 +60,7 @@ impl Environment { Environment { config: self.config, tracker: self.tracker.clone(), + whitelist_manager: self.whitelist_manager.clone(), registar: self.registar.clone(), server: self.server.start(self.tracker, self.registar.give_form()).await.unwrap(), } @@ -70,6 +76,7 @@ impl Environment { Environment { config: self.config, tracker: self.tracker, + whitelist_manager: self.whitelist_manager, registar: Registar::default(), server: self.server.stop().await.unwrap(), diff --git a/tests/servers/http/v1/contract.rs b/tests/servers/http/v1/contract.rs index db03f526e..37d0288f4 100644 --- a/tests/servers/http/v1/contract.rs +++ b/tests/servers/http/v1/contract.rs @@ -1261,7 +1261,7 @@ mod configured_as_whitelisted { let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); - env.tracker + env.whitelist_manager .add_torrent_to_whitelist(&info_hash) .await .expect("should add the torrent to the whitelist"); @@ -1343,7 +1343,7 @@ mod configured_as_whitelisted { .build(), ); - env.tracker + env.whitelist_manager .add_torrent_to_whitelist(&info_hash) .await .expect("should add the torrent to the whitelist");