From a9e3a33c0592f642f77faca84d84c46d25ca527a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 1 Mar 2023 18:10:49 +0000 Subject: [PATCH] refactor(http): extract Axum extractor for the URL path param key --- .../axum_implementation/extractors/key.rs | 55 +++++++++++++++++++ .../axum_implementation/extractors/mod.rs | 1 + .../axum_implementation/handlers/announce.rs | 16 +----- src/http/axum_implementation/handlers/auth.rs | 6 +- .../axum_implementation/handlers/scrape.rs | 18 +----- 5 files changed, 66 insertions(+), 30 deletions(-) create mode 100644 src/http/axum_implementation/extractors/key.rs diff --git a/src/http/axum_implementation/extractors/key.rs b/src/http/axum_implementation/extractors/key.rs new file mode 100644 index 000000000..6cc2f13e8 --- /dev/null +++ b/src/http/axum_implementation/extractors/key.rs @@ -0,0 +1,55 @@ +use std::panic::Location; + +use axum::async_trait; +use axum::extract::{FromRequestParts, Path}; +use axum::http::request::Parts; +use axum::response::{IntoResponse, Response}; + +use crate::http::axum_implementation::handlers::auth::{self, KeyIdParam}; +use crate::http::axum_implementation::responses; +use crate::tracker::auth::KeyId; + +pub struct ExtractKeyId(pub KeyId); + +#[async_trait] +impl FromRequestParts for ExtractKeyId +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + match Path::::from_request_parts(parts, state).await { + Ok(key_id_param) => { + let Ok(key_id) = key_id_param.0.value().parse::() else { + return Err(responses::error::Error::from( + auth::Error::InvalidKeyFormat { + location: Location::caller() + }) + .into_response()) + }; + Ok(ExtractKeyId(key_id)) + } + Err(rejection) => match rejection { + axum::extract::rejection::PathRejection::FailedToDeserializePathParams(_) => { + return Err(responses::error::Error::from(auth::Error::InvalidKeyFormat { + location: Location::caller(), + }) + .into_response()) + } + axum::extract::rejection::PathRejection::MissingPathParams(_) => { + return Err(responses::error::Error::from(auth::Error::MissingAuthKey { + location: Location::caller(), + }) + .into_response()) + } + _ => { + return Err(responses::error::Error::from(auth::Error::CannotExtractKeyParam { + location: Location::caller(), + }) + .into_response()) + } + }, + } + } +} diff --git a/src/http/axum_implementation/extractors/mod.rs b/src/http/axum_implementation/extractors/mod.rs index 380eeda6d..e6d9e8c67 100644 --- a/src/http/axum_implementation/extractors/mod.rs +++ b/src/http/axum_implementation/extractors/mod.rs @@ -1,4 +1,5 @@ pub mod announce_request; +pub mod key; pub mod peer_ip; pub mod remote_client_ip; pub mod scrape_request; diff --git a/src/http/axum_implementation/handlers/announce.rs b/src/http/axum_implementation/handlers/announce.rs index e4cd476fa..93dbc8115 100644 --- a/src/http/axum_implementation/handlers/announce.rs +++ b/src/http/axum_implementation/handlers/announce.rs @@ -3,12 +3,12 @@ use std::panic::Location; use std::sync::Arc; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; -use axum::extract::{Path, State}; +use axum::extract::State; use axum::response::{IntoResponse, Response}; use log::debug; -use super::auth::KeyIdParam; use crate::http::axum_implementation::extractors::announce_request::ExtractRequest; +use crate::http::axum_implementation::extractors::key::ExtractKeyId; 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; @@ -16,7 +16,6 @@ use crate::http::axum_implementation::requests::announce::{Announce, Compact, Ev 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; @@ -42,20 +41,11 @@ pub async fn handle_without_key( pub async fn handle_with_key( State(tracker): State>, ExtractRequest(announce_request): ExtractRequest, - Path(key_id_param): Path, + ExtractKeyId(key_id): ExtractKeyId, remote_client_ip: RemoteClientIp, ) -> Response { debug!("http announce request: {:#?}", announce_request); - // todo: extract to Axum extractor. Duplicate code in `scrape` handler. - let Ok(key_id) = key_id_param.value().parse::() else { - return responses::error::Error::from( - auth::Error::InvalidKeyFormat { - location: Location::caller() - }) - .into_response() - }; - match tracker.authenticate(&key_id).await { Ok(_) => (), Err(error) => return responses::error::Error::from(error).into_response(), diff --git a/src/http/axum_implementation/handlers/auth.rs b/src/http/axum_implementation/handlers/auth.rs index 366526664..5673ea851 100644 --- a/src/http/axum_implementation/handlers/auth.rs +++ b/src/http/axum_implementation/handlers/auth.rs @@ -18,10 +18,12 @@ impl KeyIdParam { #[derive(Debug, Error)] pub enum Error { - #[error("Missing authentication key for private tracker. Error in {location}")] + #[error("Missing authentication key param for private tracker. Error in {location}")] MissingAuthKey { location: &'static Location<'static> }, - #[error("Invalid format authentication key. Error in {location}")] + #[error("Invalid format for authentication key param. Error in {location}")] InvalidKeyFormat { location: &'static Location<'static> }, + #[error("Cannot extract authentication key param from URL path. Error in {location}")] + CannotExtractKeyParam { location: &'static Location<'static> }, } impl From for responses::error::Error { diff --git a/src/http/axum_implementation/handlers/scrape.rs b/src/http/axum_implementation/handlers/scrape.rs index 649d630b0..19d902f8e 100644 --- a/src/http/axum_implementation/handlers/scrape.rs +++ b/src/http/axum_implementation/handlers/scrape.rs @@ -1,18 +1,15 @@ -use std::panic::Location; use std::sync::Arc; -use axum::extract::{Path, State}; +use axum::extract::State; use axum::response::{IntoResponse, Response}; use log::debug; -use super::auth::KeyIdParam; +use crate::http::axum_implementation::extractors::key::ExtractKeyId; use crate::http::axum_implementation::extractors::peer_ip; use crate::http::axum_implementation::extractors::remote_client_ip::RemoteClientIp; use crate::http::axum_implementation::extractors::scrape_request::ExtractRequest; -use crate::http::axum_implementation::handlers::auth; use crate::http::axum_implementation::requests::scrape::Scrape; use crate::http::axum_implementation::{responses, services}; -use crate::tracker::auth::KeyId; use crate::tracker::Tracker; #[allow(clippy::unused_async)] @@ -34,20 +31,11 @@ pub async fn handle_without_key( pub async fn handle_with_key( State(tracker): State>, ExtractRequest(scrape_request): ExtractRequest, - Path(key_id_param): Path, + ExtractKeyId(key_id): ExtractKeyId, remote_client_ip: RemoteClientIp, ) -> Response { debug!("http scrape request: {:#?}", &scrape_request); - // todo: extract to Axum extractor. Duplicate code in `announce` handler. - let Ok(key_id) = key_id_param.value().parse::() else { - return responses::error::Error::from( - auth::Error::InvalidKeyFormat { - location: Location::caller() - }) - .into_response() - }; - match tracker.authenticate(&key_id).await { Ok(_) => (), Err(_) => return handle_fake_scrape(&tracker, &scrape_request, &remote_client_ip).await,