Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axum HTTP tracker: announce request in private mode #198

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions src/http/axum_implementation/handlers/announce.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,70 @@
use std::net::{IpAddr, SocketAddr};
use std::panic::Location;
use std::sync::Arc;

use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
use axum::extract::State;
use axum::extract::{Path, State};
use axum::response::{IntoResponse, Response};
use log::debug;

use crate::http::axum_implementation::extractors::announce_request::ExtractRequest;
use crate::http::axum_implementation::extractors::peer_ip;
use crate::http::axum_implementation::extractors::remote_client_ip::RemoteClientIp;
use crate::http::axum_implementation::handlers::auth;
use crate::http::axum_implementation::requests::announce::{Announce, Compact, Event};
use crate::http::axum_implementation::responses::announce;
use crate::http::axum_implementation::responses::{self, announce};
use crate::http::axum_implementation::services;
use crate::protocol::clock::{Current, Time};
use crate::tracker::auth::KeyId;
use crate::tracker::peer::Peer;
use crate::tracker::Tracker;

#[allow(clippy::unused_async)]
pub async fn handle(
pub async fn handle_without_key(
State(tracker): State<Arc<Tracker>>,
ExtractRequest(announce_request): ExtractRequest,
remote_client_ip: RemoteClientIp,
) -> Response {
debug!("http announce request: {:#?}", announce_request);

let peer_ip = match peer_ip::resolve(tracker.config.on_reverse_proxy, &remote_client_ip) {
if tracker.is_private() {
return responses::error::Error::from(auth::Error::MissingAuthKey {
location: Location::caller(),
})
.into_response();
}

handle(&tracker, &announce_request, &remote_client_ip).await
}

#[allow(clippy::unused_async)]
pub async fn handle_with_key(
State(tracker): State<Arc<Tracker>>,
ExtractRequest(announce_request): ExtractRequest,
Path(key_id): Path<KeyId>,
remote_client_ip: RemoteClientIp,
) -> Response {
debug!("http announce request: {:#?}", announce_request);

match auth::authenticate(&key_id, &tracker).await {
Ok(_) => (),
Err(error) => return responses::error::Error::from(error).into_response(),
}

handle(&tracker, &announce_request, &remote_client_ip).await
}

async fn handle(tracker: &Arc<Tracker>, announce_request: &Announce, remote_client_ip: &RemoteClientIp) -> Response {
let peer_ip = match peer_ip::resolve(tracker.config.on_reverse_proxy, remote_client_ip) {
Ok(peer_ip) => peer_ip,
Err(err) => return err,
};

let mut peer = peer_from_request(&announce_request, &peer_ip);
let mut peer = peer_from_request(announce_request, &peer_ip);

let announce_data = services::announce::invoke(tracker.clone(), announce_request.info_hash, &mut peer).await;

match announce_request.compact {
match &announce_request.compact {
Some(compact) => match compact {
Compact::Accepted => announce::Compact::from(announce_data).into_response(),
Compact::NotAccepted => announce::NonCompact::from(announce_data).into_response(),
Expand Down
41 changes: 41 additions & 0 deletions src/http/axum_implementation/handlers/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::panic::Location;
use std::sync::Arc;

use thiserror::Error;

use crate::http::axum_implementation::responses;
use crate::tracker::auth::{self, KeyId};
use crate::tracker::Tracker;

#[derive(Debug, Error)]
pub enum Error {
#[error("Missing authentication key for private tracker. Error in {location}")]
MissingAuthKey { location: &'static Location<'static> },
}

/// # Errors
///
/// Will return an error if the the authentication key cannot be verified.
pub async fn authenticate(key_id: &KeyId, tracker: &Arc<Tracker>) -> Result<(), auth::Error> {
if tracker.is_private() {
tracker.verify_auth_key(key_id).await
} else {
Ok(())
}
}

impl From<Error> for responses::error::Error {
fn from(err: Error) -> Self {
responses::error::Error {
failure_reason: format!("Authentication error: {err}"),
}
}
}

impl From<auth::Error> for responses::error::Error {
fn from(err: auth::Error) -> Self {
responses::error::Error {
failure_reason: format!("Authentication error: {err}"),
}
}
}
1 change: 1 addition & 0 deletions src/http/axum_implementation/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod announce;
pub mod auth;
pub mod scrape;
pub mod status;
3 changes: 2 additions & 1 deletion src/http/axum_implementation/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ pub fn router(tracker: &Arc<Tracker>) -> Router {
// Status
.route("/status", get(status::handle))
// Announce request
.route("/announce", get(announce::handle).with_state(tracker.clone()))
.route("/announce", get(announce::handle_without_key).with_state(tracker.clone()))
.route("/announce/:key", get(announce::handle_with_key).with_state(tracker.clone()))
// Scrape request
.route("/scrape", get(scrape::handle).with_state(tracker.clone()))
// Add extension to get the client IP from the connection info
Expand Down
22 changes: 6 additions & 16 deletions tests/http/asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,22 +140,6 @@ pub async fn assert_torrent_not_in_whitelist_error_response(response: Response)
assert_bencoded_error(&response.text().await.unwrap(), "is not whitelisted", Location::caller());
}

pub async fn assert_peer_not_authenticated_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(
&response.text().await.unwrap(),
"The peer is not authenticated",
Location::caller(),
);
}

pub async fn assert_invalid_authentication_key_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(&response.text().await.unwrap(), "is not valid", Location::caller());
}

pub async fn assert_could_not_find_remote_address_on_xff_header_error_response(response: Response) {
assert_eq!(response.status(), 200);

Expand Down Expand Up @@ -199,3 +183,9 @@ pub async fn assert_cannot_parse_query_params_error_response(response: Response,
Location::caller(),
);
}

pub async fn assert_authentication_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(&response.text().await.unwrap(), "Authentication error", Location::caller());
}
19 changes: 19 additions & 0 deletions tests/http/asserts_warp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::panic::Location;

/// todo: this mod should be removed when we remove the Warp implementation for the HTTP tracker.
use reqwest::Response;

use super::responses::announce_warp::WarpAnnounce;
use crate::http::asserts::assert_bencoded_error;

pub async fn assert_warp_announce_response(response: Response, expected_announce_response: &WarpAnnounce) {
assert_eq!(response.status(), 200);
Expand All @@ -13,3 +16,19 @@ pub async fn assert_warp_announce_response(response: Response, expected_announce

assert_eq!(announce_response, *expected_announce_response);
}

pub async fn assert_warp_peer_not_authenticated_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(
&response.text().await.unwrap(),
"The peer is not authenticated",
Location::caller(),
);
}

pub async fn assert_warp_invalid_authentication_key_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(&response.text().await.unwrap(), "is not valid", Location::caller());
}
28 changes: 11 additions & 17 deletions tests/http_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,9 +1085,9 @@ mod warp_http_tracker_server {
use torrust_tracker::protocol::info_hash::InfoHash;
use torrust_tracker::tracker::auth::KeyId;

use crate::http::asserts::{
assert_invalid_authentication_key_error_response, assert_is_announce_response,
assert_peer_not_authenticated_error_response,
use crate::http::asserts::assert_is_announce_response;
use crate::http::asserts_warp::{
assert_warp_invalid_authentication_key_error_response, assert_warp_peer_not_authenticated_error_response,
};
use crate::http::client::Client;
use crate::http::requests::announce::QueryBuilder;
Expand Down Expand Up @@ -1120,7 +1120,7 @@ mod warp_http_tracker_server {
.announce(&QueryBuilder::default().with_info_hash(&info_hash).query())
.await;

assert_peer_not_authenticated_error_response(response).await;
assert_warp_peer_not_authenticated_error_response(response).await;
}

#[tokio::test]
Expand All @@ -1134,7 +1134,7 @@ mod warp_http_tracker_server {
.announce(&QueryBuilder::default().query())
.await;

assert_invalid_authentication_key_error_response(response).await;
assert_warp_invalid_authentication_key_error_response(response).await;
}
}

Expand Down Expand Up @@ -2539,16 +2539,12 @@ mod axum_http_tracker_server {
use torrust_tracker::protocol::info_hash::InfoHash;
use torrust_tracker::tracker::auth::KeyId;

use crate::http::asserts::{
assert_invalid_authentication_key_error_response, assert_is_announce_response,
assert_peer_not_authenticated_error_response,
};
use crate::http::asserts::{assert_authentication_error_response, assert_is_announce_response};
use crate::http::client::Client;
use crate::http::requests::announce::QueryBuilder;
use crate::http::server::start_private_http_tracker;

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_respond_to_authenticated_peers() {
let http_tracker_server = start_private_http_tracker(Version::Axum).await;

Expand All @@ -2565,8 +2561,7 @@ mod axum_http_tracker_server {
assert_is_announce_response(response).await;
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_fail_if_the_peer_has_not_provided_the_authentication_key() {
let http_tracker_server = start_private_http_tracker(Version::Axum).await;

Expand All @@ -2576,11 +2571,10 @@ mod axum_http_tracker_server {
.announce(&QueryBuilder::default().with_info_hash(&info_hash).query())
.await;

assert_peer_not_authenticated_error_response(response).await;
assert_authentication_error_response(response).await;
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_fail_if_the_peer_authentication_key_is_not_valid() {
let http_tracker_server = start_private_http_tracker(Version::Axum).await;

Expand All @@ -2591,7 +2585,7 @@ mod axum_http_tracker_server {
.announce(&QueryBuilder::default().query())
.await;

assert_invalid_authentication_key_error_response(response).await;
assert_authentication_error_response(response).await;
}
}

Expand Down