From 583b305ed32a310f5897be7de943df8e4975e751 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 30 Jul 2024 10:55:34 +0100 Subject: [PATCH] test: [#874] new key generation endpoint --- tests/servers/api/v1/asserts.rs | 35 ++- tests/servers/api/v1/client.rs | 28 +- .../api/v1/contract/context/auth_key.rs | 240 +++++++++++++++--- 3 files changed, 267 insertions(+), 36 deletions(-) diff --git a/tests/servers/api/v1/asserts.rs b/tests/servers/api/v1/asserts.rs index 955293db1..ba906f65f 100644 --- a/tests/servers/api/v1/asserts.rs +++ b/tests/servers/api/v1/asserts.rs @@ -61,6 +61,12 @@ pub async fn assert_bad_request(response: Response, body: &str) { assert_eq!(response.text().await.unwrap(), body); } +pub async fn assert_unprocessable_content(response: Response, text: &str) { + assert_eq!(response.status(), 422); + assert_eq!(response.headers().get("content-type").unwrap(), "text/plain; charset=utf-8"); + assert!(response.text().await.unwrap().contains(text)); +} + pub async fn assert_not_found(response: Response) { assert_eq!(response.status(), 404); // todo: missing header in the response @@ -82,10 +88,37 @@ pub async fn assert_invalid_infohash_param(response: Response, invalid_infohash: .await; } -pub async fn assert_invalid_auth_key_param(response: Response, invalid_auth_key: &str) { +pub async fn assert_invalid_auth_key_get_param(response: Response, invalid_auth_key: &str) { assert_bad_request(response, &format!("Invalid auth key id param \"{}\"", &invalid_auth_key)).await; } +pub async fn assert_invalid_auth_key_post_param(response: Response, invalid_auth_key: &str) { + assert_bad_request( + response, + &format!( + "Invalid URL: invalid auth key: string \"{}\", ParseKeyError", + &invalid_auth_key + ), + ) + .await; +} + +pub async fn _assert_unprocessable_auth_key_param(response: Response, _invalid_value: &str) { + assert_unprocessable_content( + response, + "Failed to deserialize the JSON body into the target type: seconds_valid: invalid type", + ) + .await; +} + +pub async fn assert_unprocessable_auth_key_duration_param(response: Response, _invalid_value: &str) { + assert_unprocessable_content( + response, + "Failed to deserialize the JSON body into the target type: seconds_valid: invalid type", + ) + .await; +} + pub async fn assert_invalid_key_duration_param(response: Response, invalid_key_duration: &str) { assert_bad_request( response, diff --git a/tests/servers/api/v1/client.rs b/tests/servers/api/v1/client.rs index 61e98e742..91f18acac 100644 --- a/tests/servers/api/v1/client.rs +++ b/tests/servers/api/v1/client.rs @@ -1,4 +1,5 @@ use reqwest::Response; +use serde::Serialize; use crate::common::http::{Query, QueryParam, ReqwestQuery}; use crate::servers::api::connection_info::ConnectionInfo; @@ -18,7 +19,11 @@ impl Client { } pub async fn generate_auth_key(&self, seconds_valid: i32) -> Response { - self.post(&format!("key/{}", &seconds_valid)).await + self.post_empty(&format!("key/{}", &seconds_valid)).await + } + + pub async fn add_auth_key(&self, add_key_form: AddKeyForm) -> Response { + self.post_form("keys", &add_key_form).await } pub async fn delete_auth_key(&self, key: &str) -> Response { @@ -30,7 +35,7 @@ impl Client { } pub async fn whitelist_a_torrent(&self, info_hash: &str) -> Response { - self.post(&format!("whitelist/{}", &info_hash)).await + self.post_empty(&format!("whitelist/{}", &info_hash)).await } pub async fn remove_torrent_from_whitelist(&self, info_hash: &str) -> Response { @@ -63,10 +68,20 @@ impl Client { self.get_request_with_query(path, query).await } - pub async fn post(&self, path: &str) -> Response { + pub async fn post_empty(&self, path: &str) -> Response { + reqwest::Client::new() + .post(self.base_url(path).clone()) + .query(&ReqwestQuery::from(self.query_with_token())) + .send() + .await + .unwrap() + } + + pub async fn post_form(&self, path: &str, form: &T) -> Response { reqwest::Client::new() .post(self.base_url(path).clone()) .query(&ReqwestQuery::from(self.query_with_token())) + .json(&form) .send() .await .unwrap() @@ -114,3 +129,10 @@ pub async fn get(path: &str, query: Option) -> Response { None => reqwest::Client::builder().build().unwrap().get(path).send().await.unwrap(), } } + +#[derive(Serialize, Debug)] +pub struct AddKeyForm { + #[serde(rename = "key")] + pub opt_key: Option, + pub seconds_valid: u64, +} diff --git a/tests/servers/api/v1/contract/context/auth_key.rs b/tests/servers/api/v1/contract/context/auth_key.rs index f9630bafe..f02267b8b 100644 --- a/tests/servers/api/v1/contract/context/auth_key.rs +++ b/tests/servers/api/v1/contract/context/auth_key.rs @@ -1,23 +1,28 @@ use std::time::Duration; +use serde::Serialize; use torrust_tracker::core::auth::Key; use torrust_tracker_test_helpers::configuration; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::v1::asserts::{ assert_auth_key_utf8, assert_failed_to_delete_key, assert_failed_to_generate_key, assert_failed_to_reload_keys, - assert_invalid_auth_key_param, assert_invalid_key_duration_param, assert_ok, assert_token_not_valid, assert_unauthorized, + assert_invalid_auth_key_get_param, assert_invalid_auth_key_post_param, assert_ok, assert_token_not_valid, + assert_unauthorized, assert_unprocessable_auth_key_duration_param, }; -use crate::servers::api::v1::client::Client; +use crate::servers::api::v1::client::{AddKeyForm, Client}; use crate::servers::api::{force_database_error, Started}; #[tokio::test] -async fn should_allow_generating_a_new_auth_key() { +async fn should_allow_generating_a_new_random_auth_key() { let env = Started::new(&configuration::ephemeral().into()).await; - let seconds_valid = 60; - - let response = Client::new(env.get_connection_info()).generate_auth_key(seconds_valid).await; + let response = Client::new(env.get_connection_info()) + .add_auth_key(AddKeyForm { + opt_key: None, + seconds_valid: 60, + }) + .await; let auth_key_resource = assert_auth_key_utf8(response).await; @@ -32,43 +37,49 @@ async fn should_allow_generating_a_new_auth_key() { } #[tokio::test] -async fn should_not_allow_generating_a_new_auth_key_for_unauthenticated_users() { +async fn should_allow_uploading_a_preexisting_auth_key() { let env = Started::new(&configuration::ephemeral().into()).await; - let seconds_valid = 60; - - let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .generate_auth_key(seconds_valid) + let response = Client::new(env.get_connection_info()) + .add_auth_key(AddKeyForm { + opt_key: Some("Xc1L4PbQJSFGlrgSRZl8wxSFAuMa21z5".to_string()), + seconds_valid: 60, + }) .await; - assert_token_not_valid(response).await; - - let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .generate_auth_key(seconds_valid) - .await; + let auth_key_resource = assert_auth_key_utf8(response).await; - assert_unauthorized(response).await; + // Verify the key with the tracker + assert!(env + .tracker + .verify_auth_key(&auth_key_resource.key.parse::().unwrap()) + .await + .is_ok()); env.stop().await; } #[tokio::test] -async fn should_fail_generating_a_new_auth_key_when_the_key_duration_is_invalid() { +async fn should_not_allow_generating_a_new_auth_key_for_unauthenticated_users() { let env = Started::new(&configuration::ephemeral().into()).await; - let invalid_key_durations = [ - // "", it returns 404 - // " ", it returns 404 - "-1", "text", - ]; + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) + .add_auth_key(AddKeyForm { + opt_key: None, + seconds_valid: 60, + }) + .await; - for invalid_key_duration in invalid_key_durations { - let response = Client::new(env.get_connection_info()) - .post(&format!("key/{invalid_key_duration}")) - .await; + assert_token_not_valid(response).await; - assert_invalid_key_duration_param(response, invalid_key_duration).await; - } + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) + .add_auth_key(AddKeyForm { + opt_key: None, + seconds_valid: 60, + }) + .await; + + assert_unauthorized(response).await; env.stop().await; } @@ -79,8 +90,12 @@ async fn should_fail_when_the_auth_key_cannot_be_generated() { force_database_error(&env.tracker); - let seconds_valid = 60; - let response = Client::new(env.get_connection_info()).generate_auth_key(seconds_valid).await; + let response = Client::new(env.get_connection_info()) + .add_auth_key(AddKeyForm { + opt_key: None, + seconds_valid: 60, + }) + .await; assert_failed_to_generate_key(response).await; @@ -107,6 +122,77 @@ async fn should_allow_deleting_an_auth_key() { env.stop().await; } +#[tokio::test] +async fn should_fail_generating_a_new_auth_key_when_the_provided_key_is_invalid() { + #[derive(Serialize, Debug)] + pub struct InvalidAddKeyForm { + #[serde(rename = "key")] + pub opt_key: Option, + pub seconds_valid: u64, + } + + let env = Started::new(&configuration::ephemeral().into()).await; + + let invalid_keys = [ + // "", it returns 404 + // " ", it returns 404 + "-1", // Not a string + "invalid", // Invalid string + "GQEs2ZNcCm9cwEV9dBpcPB5OwNFWFiR", // Not a 32-char string + // "%QEs2ZNcCm9cwEV9dBpcPB5OwNFWFiRd", // Invalid char. todo: this doesn't fail + ]; + + for invalid_key in invalid_keys { + let response = Client::new(env.get_connection_info()) + .post_form( + "keys", + &InvalidAddKeyForm { + opt_key: Some(invalid_key.to_string()), + seconds_valid: 60, + }, + ) + .await; + + assert_invalid_auth_key_post_param(response, invalid_key).await; + } + + env.stop().await; +} + +#[tokio::test] +async fn should_fail_generating_a_new_auth_key_when_the_key_duration_is_invalid() { + #[derive(Serialize, Debug)] + pub struct InvalidAddKeyForm { + #[serde(rename = "key")] + pub opt_key: Option, + pub seconds_valid: String, + } + + let env = Started::new(&configuration::ephemeral().into()).await; + + let invalid_key_durations = [ + // "", it returns 404 + // " ", it returns 404 + "-1", "text", + ]; + + for invalid_key_duration in invalid_key_durations { + let response = Client::new(env.get_connection_info()) + .post_form( + "keys", + &InvalidAddKeyForm { + opt_key: None, + seconds_valid: invalid_key_duration.to_string(), + }, + ) + .await; + + assert_unprocessable_auth_key_duration_param(response, invalid_key_duration).await; + } + + env.stop().await; +} + #[tokio::test] async fn should_fail_deleting_an_auth_key_when_the_key_id_is_invalid() { let env = Started::new(&configuration::ephemeral().into()).await; @@ -124,7 +210,7 @@ async fn should_fail_deleting_an_auth_key_when_the_key_id_is_invalid() { for invalid_auth_key in &invalid_auth_keys { let response = Client::new(env.get_connection_info()).delete_auth_key(invalid_auth_key).await; - assert_invalid_auth_key_param(response, invalid_auth_key).await; + assert_invalid_auth_key_get_param(response, invalid_auth_key).await; } env.stop().await; @@ -247,3 +333,93 @@ async fn should_not_allow_reloading_keys_for_unauthenticated_users() { env.stop().await; } + +mod deprecated_generate_key_endpoint { + + use torrust_tracker::core::auth::Key; + use torrust_tracker_test_helpers::configuration; + + use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; + use crate::servers::api::v1::asserts::{ + assert_auth_key_utf8, assert_failed_to_generate_key, assert_invalid_key_duration_param, assert_token_not_valid, + assert_unauthorized, + }; + use crate::servers::api::v1::client::Client; + use crate::servers::api::{force_database_error, Started}; + + #[tokio::test] + async fn should_allow_generating_a_new_auth_key() { + let env = Started::new(&configuration::ephemeral().into()).await; + + let seconds_valid = 60; + + let response = Client::new(env.get_connection_info()).generate_auth_key(seconds_valid).await; + + let auth_key_resource = assert_auth_key_utf8(response).await; + + // Verify the key with the tracker + assert!(env + .tracker + .verify_auth_key(&auth_key_resource.key.parse::().unwrap()) + .await + .is_ok()); + + env.stop().await; + } + + #[tokio::test] + async fn should_not_allow_generating_a_new_auth_key_for_unauthenticated_users() { + let env = Started::new(&configuration::ephemeral().into()).await; + + let seconds_valid = 60; + + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) + .generate_auth_key(seconds_valid) + .await; + + assert_token_not_valid(response).await; + + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) + .generate_auth_key(seconds_valid) + .await; + + assert_unauthorized(response).await; + + env.stop().await; + } + + #[tokio::test] + async fn should_fail_generating_a_new_auth_key_when_the_key_duration_is_invalid() { + let env = Started::new(&configuration::ephemeral().into()).await; + + let invalid_key_durations = [ + // "", it returns 404 + // " ", it returns 404 + "-1", "text", + ]; + + for invalid_key_duration in invalid_key_durations { + let response = Client::new(env.get_connection_info()) + .post_empty(&format!("key/{invalid_key_duration}")) + .await; + + assert_invalid_key_duration_param(response, invalid_key_duration).await; + } + + env.stop().await; + } + + #[tokio::test] + async fn should_fail_when_the_auth_key_cannot_be_generated() { + let env = Started::new(&configuration::ephemeral().into()).await; + + force_database_error(&env.tracker); + + let seconds_valid = 60; + let response = Client::new(env.get_connection_info()).generate_auth_key(seconds_valid).await; + + assert_failed_to_generate_key(response).await; + + env.stop().await; + } +}