mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-10-31 07:56:28 +00:00 
			
		
		
		
	feat(auth): Implement Tenant token
Make meilisearch support JWT authentication signed with meilisearch API keys using HS256, HS384 or HS512 algorithms. Related spec: https://github.com/meilisearch/specifications/pull/89 Fix #1991
This commit is contained in:
		| @@ -5,7 +5,7 @@ use once_cell::sync::Lazy; | ||||
| use serde_json::{json, Value}; | ||||
| use std::collections::{HashMap, HashSet}; | ||||
|  | ||||
| static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'static str>>> = | ||||
| pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'static str>>> = | ||||
|     Lazy::new(|| { | ||||
|         hashmap! { | ||||
|             ("POST",    "/indexes/products/search") =>                         hashset!{"search", "*"}, | ||||
| @@ -49,7 +49,7 @@ static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'stat | ||||
|         } | ||||
|     }); | ||||
|  | ||||
| static ALL_ACTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| { | ||||
| pub static ALL_ACTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| { | ||||
|     AUTHORIZATIONS | ||||
|         .values() | ||||
|         .cloned() | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| mod api_keys; | ||||
| mod authorization; | ||||
| mod payload; | ||||
| mod tenant_token; | ||||
|  | ||||
| use crate::common::Server; | ||||
| use actix_web::http::StatusCode; | ||||
|   | ||||
							
								
								
									
										574
									
								
								meilisearch-http/tests/auth/tenant_token.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										574
									
								
								meilisearch-http/tests/auth/tenant_token.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,574 @@ | ||||
| use crate::common::Server; | ||||
| use chrono::{Duration, Utc}; | ||||
| use maplit::hashmap; | ||||
| use once_cell::sync::Lazy; | ||||
| use serde_json::{json, Value}; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use super::authorization::{ALL_ACTIONS, AUTHORIZATIONS}; | ||||
|  | ||||
| fn generate_tenant_token(parent_key: impl AsRef<str>, mut body: HashMap<&str, Value>) -> String { | ||||
|     use jsonwebtoken::{encode, EncodingKey, Header}; | ||||
|  | ||||
|     let key_id = &parent_key.as_ref()[..8]; | ||||
|     body.insert("apiKeyPrefix", json!(key_id)); | ||||
|     encode( | ||||
|         &Header::default(), | ||||
|         &body, | ||||
|         &EncodingKey::from_secret(parent_key.as_ref().as_bytes()), | ||||
|     ) | ||||
|     .unwrap() | ||||
| } | ||||
|  | ||||
| static DOCUMENTS: Lazy<Value> = Lazy::new(|| { | ||||
|     json!([ | ||||
|         { | ||||
|             "title": "Shazam!", | ||||
|             "id": "287947", | ||||
|             "color": ["green", "blue"] | ||||
|         }, | ||||
|         { | ||||
|             "title": "Captain Marvel", | ||||
|             "id": "299537", | ||||
|             "color": ["yellow", "blue"] | ||||
|         }, | ||||
|         { | ||||
|             "title": "Escape Room", | ||||
|             "id": "522681", | ||||
|             "color": ["yellow", "red"] | ||||
|         }, | ||||
|         { | ||||
|             "title": "How to Train Your Dragon: The Hidden World", | ||||
|             "id": "166428", | ||||
|             "color": ["green", "red"] | ||||
|         }, | ||||
|         { | ||||
|             "title": "Glass", | ||||
|             "id": "450465", | ||||
|             "color": ["blue", "red"] | ||||
|         } | ||||
|     ]) | ||||
| }); | ||||
|  | ||||
| static INVALID_RESPONSE: Lazy<Value> = Lazy::new(|| { | ||||
|     json!({"message": "The provided API key is invalid.", | ||||
|         "code": "invalid_api_key", | ||||
|         "type": "auth", | ||||
|         "link": "https://docs.meilisearch.com/errors#invalid_api_key" | ||||
|     }) | ||||
| }); | ||||
|  | ||||
| static ACCEPTED_KEYS: Lazy<Vec<Value>> = Lazy::new(|| { | ||||
|     vec![ | ||||
|         json!({ | ||||
|             "indexes": ["*"], | ||||
|             "actions": ["*"], | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|         json!({ | ||||
|             "indexes": ["*"], | ||||
|             "actions": ["search"], | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|         json!({ | ||||
|             "indexes": ["sales"], | ||||
|             "actions": ["*"], | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|         json!({ | ||||
|             "indexes": ["sales"], | ||||
|             "actions": ["search"], | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|     ] | ||||
| }); | ||||
|  | ||||
| static REFUSED_KEYS: Lazy<Vec<Value>> = Lazy::new(|| { | ||||
|     vec![ | ||||
|         // no search action | ||||
|         json!({ | ||||
|             "indexes": ["*"], | ||||
|             "actions": ALL_ACTIONS.iter().cloned().filter(|a| *a != "search" && *a != "*").collect::<Vec<_>>(), | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|         json!({ | ||||
|             "indexes": ["sales"], | ||||
|             "actions": ALL_ACTIONS.iter().cloned().filter(|a| *a != "search" && *a != "*").collect::<Vec<_>>(), | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|         // bad index | ||||
|         json!({ | ||||
|             "indexes": ["products"], | ||||
|             "actions": ["*"], | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|         json!({ | ||||
|             "indexes": ["products"], | ||||
|             "actions": ["search"], | ||||
|             "expiresAt": Utc::now() + Duration::days(1) | ||||
|         }), | ||||
|     ] | ||||
| }); | ||||
|  | ||||
| macro_rules! compute_autorized_search { | ||||
|     ($tenant_tokens:expr, $filter:expr, $expected_count:expr) => { | ||||
|         let mut server = Server::new_auth().await; | ||||
|         server.use_api_key("MASTER_KEY"); | ||||
|         let index = server.index("sales"); | ||||
|         let documents = DOCUMENTS.clone(); | ||||
|         index.add_documents(documents, None).await; | ||||
|         index.wait_task(0).await; | ||||
|         index | ||||
|             .update_settings(json!({"filterableAttributes": ["color"]})) | ||||
|             .await; | ||||
|         index.wait_task(1).await; | ||||
|         drop(index); | ||||
|  | ||||
|         for key_content in ACCEPTED_KEYS.iter() { | ||||
|             server.use_api_key("MASTER_KEY"); | ||||
|             let (response, code) = server.add_api_key(key_content.clone()).await; | ||||
|             assert_eq!(code, 201); | ||||
|             let key = response["key"].as_str().unwrap(); | ||||
|  | ||||
|             for tenant_token in $tenant_tokens.iter() { | ||||
|                 let web_token = generate_tenant_token(&key, tenant_token.clone()); | ||||
|                 server.use_api_key(&web_token); | ||||
|                 let index = server.index("sales"); | ||||
|                 index | ||||
|                     .search(json!({ "filter": $filter }), |response, code| { | ||||
|                         assert_eq!( | ||||
|                             code, 200, | ||||
|                             "{} using tenant_token: {:?} generated with parent_key: {:?}", | ||||
|                             response, tenant_token, key_content | ||||
|                         ); | ||||
|                         assert_eq!( | ||||
|                             response["hits"].as_array().unwrap().len(), | ||||
|                             $expected_count, | ||||
|                             "{} using tenant_token: {:?} generated with parent_key: {:?}", | ||||
|                             response, | ||||
|                             tenant_token, | ||||
|                             key_content | ||||
|                         ); | ||||
|                     }) | ||||
|                     .await; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! compute_forbidden_search { | ||||
|     ($tenant_tokens:expr, $parent_keys:expr) => { | ||||
|         let mut server = Server::new_auth().await; | ||||
|         server.use_api_key("MASTER_KEY"); | ||||
|         let index = server.index("sales"); | ||||
|         let documents = DOCUMENTS.clone(); | ||||
|         index.add_documents(documents, None).await; | ||||
|         index.wait_task(0).await; | ||||
|         drop(index); | ||||
|  | ||||
|         for key_content in $parent_keys.iter() { | ||||
|             server.use_api_key("MASTER_KEY"); | ||||
|             let (response, code) = server.add_api_key(key_content.clone()).await; | ||||
|             assert_eq!(code, 201, "{:?}", response); | ||||
|             let key = response["key"].as_str().unwrap(); | ||||
|  | ||||
|             for tenant_token in $tenant_tokens.iter() { | ||||
|                 let web_token = generate_tenant_token(&key, tenant_token.clone()); | ||||
|                 server.use_api_key(&web_token); | ||||
|                 let index = server.index("sales"); | ||||
|                 index | ||||
|                     .search(json!({}), |response, code| { | ||||
|                         assert_eq!( | ||||
|                             response, | ||||
|                             INVALID_RESPONSE.clone(), | ||||
|                             "{} using tenant_token: {:?} generated with parent_key: {:?}", | ||||
|                             response, | ||||
|                             tenant_token, | ||||
|                             key_content | ||||
|                         ); | ||||
|                         assert_eq!( | ||||
|                             code, 403, | ||||
|                             "{} using tenant_token: {:?} generated with parent_key: {:?}", | ||||
|                             response, tenant_token, key_content | ||||
|                         ); | ||||
|                     }) | ||||
|                     .await; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn search_authorized_simple_token() { | ||||
|     let tenant_tokens = vec![ | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["*"]), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["sales"]), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {}}), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": Value::Null}), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["*"]), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {}}), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": Value::Null}), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["sales"]), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     compute_autorized_search!(tenant_tokens, {}, 5); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn search_authorized_filter_token() { | ||||
|     let tenant_tokens = vec![ | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {"filter": "color = blue"}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {"filter": "color = blue"}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {"filter": ["color = blue"]}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {"filter": ["color = blue"]}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         // filter on sales should override filters on * | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {"filter": "color = green"}, | ||||
|                 "sales": {"filter": "color = blue"} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {}, | ||||
|                 "sales": {"filter": "color = blue"} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {"filter": "color = green"}, | ||||
|                 "sales": {"filter": ["color = blue"]} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {}, | ||||
|                 "sales": {"filter": ["color = blue"]} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     compute_autorized_search!(tenant_tokens, {}, 3); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn filter_search_authorized_filter_token() { | ||||
|     let tenant_tokens = vec![ | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {"filter": "color = blue"}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {"filter": "color = blue"}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {"filter": ["color = blue"]}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {"filter": ["color = blue"]}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         // filter on sales should override filters on * | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {"filter": "color = green"}, | ||||
|                 "sales": {"filter": "color = blue"} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {}, | ||||
|                 "sales": {"filter": "color = blue"} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {"filter": "color = green"}, | ||||
|                 "sales": {"filter": ["color = blue"]} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({ | ||||
|                 "*": {}, | ||||
|                 "sales": {"filter": ["color = blue"]} | ||||
|             }), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     compute_autorized_search!(tenant_tokens, "color = yellow", 1); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn error_search_token_forbidden_parent_key() { | ||||
|     let tenant_tokens = vec![ | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": Value::Null}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["*"]), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": Value::Null}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["sales"]), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     compute_forbidden_search!(tenant_tokens, REFUSED_KEYS); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn error_search_forbidden_token() { | ||||
|     let tenant_tokens = vec![ | ||||
|         // bad index | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"products": {}}), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["products"]), | ||||
|             "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"products": {}}), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"products": Value::Null}), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["products"]), | ||||
|             "exp" => Value::Null | ||||
|         }, | ||||
|         // expired token | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": {}}), | ||||
|             "exp" => json!((Utc::now() - Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"*": Value::Null}), | ||||
|             "exp" => json!((Utc::now() - Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["*"]), | ||||
|             "exp" => json!((Utc::now() - Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": {}}), | ||||
|             "exp" => json!((Utc::now() - Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!({"sales": Value::Null}), | ||||
|             "exp" => json!((Utc::now() - Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|         hashmap! { | ||||
|             "searchRules" => json!(["sales"]), | ||||
|             "exp" => json!((Utc::now() - Duration::hours(1)).timestamp()) | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     compute_forbidden_search!(tenant_tokens, ACCEPTED_KEYS); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn error_access_forbidden_routes() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["*"], | ||||
|         "actions": ["*"], | ||||
|         "expiresAt": (Utc::now() + Duration::hours(1)), | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|  | ||||
|     let tenant_token = hashmap! { | ||||
|         "searchRules" => json!(["*"]), | ||||
|         "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|     }; | ||||
|     let web_token = generate_tenant_token(&key, tenant_token); | ||||
|     server.use_api_key(&web_token); | ||||
|  | ||||
|     for ((method, route), actions) in AUTHORIZATIONS.iter() { | ||||
|         if !actions.contains("search") { | ||||
|             let (response, code) = server.dummy_request(method, route).await; | ||||
|             assert_eq!(response, INVALID_RESPONSE.clone()); | ||||
|             assert_eq!(code, 403); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn error_access_expired_parent_key() { | ||||
|     use std::{thread, time}; | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["*"], | ||||
|         "actions": ["*"], | ||||
|         "expiresAt": (Utc::now() + Duration::seconds(1)), | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|  | ||||
|     let tenant_token = hashmap! { | ||||
|         "searchRules" => json!(["*"]), | ||||
|         "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|     }; | ||||
|     let web_token = generate_tenant_token(&key, tenant_token); | ||||
|     server.use_api_key(&web_token); | ||||
|  | ||||
|     // test search request while parent_key is not expired | ||||
|     let (response, code) = server | ||||
|         .dummy_request("POST", "/indexes/products/search") | ||||
|         .await; | ||||
|     assert_ne!(response, INVALID_RESPONSE.clone()); | ||||
|     assert_ne!(code, 403); | ||||
|  | ||||
|     // wait until the key is expired. | ||||
|     thread::sleep(time::Duration::new(1, 0)); | ||||
|  | ||||
|     let (response, code) = server | ||||
|         .dummy_request("POST", "/indexes/products/search") | ||||
|         .await; | ||||
|     assert_eq!(response, INVALID_RESPONSE.clone()); | ||||
|     assert_eq!(code, 403); | ||||
| } | ||||
|  | ||||
| #[actix_rt::test] | ||||
| #[cfg_attr(target_os = "windows", ignore)] | ||||
| async fn error_access_modified_token() { | ||||
|     let mut server = Server::new_auth().await; | ||||
|     server.use_api_key("MASTER_KEY"); | ||||
|  | ||||
|     let content = json!({ | ||||
|         "indexes": ["*"], | ||||
|         "actions": ["*"], | ||||
|         "expiresAt": (Utc::now() + Duration::hours(1)), | ||||
|     }); | ||||
|  | ||||
|     let (response, code) = server.add_api_key(content).await; | ||||
|     assert_eq!(code, 201); | ||||
|     assert!(response["key"].is_string()); | ||||
|  | ||||
|     let key = response["key"].as_str().unwrap(); | ||||
|  | ||||
|     let tenant_token = hashmap! { | ||||
|         "searchRules" => json!(["products"]), | ||||
|         "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|     }; | ||||
|     let web_token = generate_tenant_token(&key, tenant_token); | ||||
|     server.use_api_key(&web_token); | ||||
|  | ||||
|     // test search request while web_token is valid | ||||
|     let (response, code) = server | ||||
|         .dummy_request("POST", "/indexes/products/search") | ||||
|         .await; | ||||
|     assert_ne!(response, INVALID_RESPONSE.clone()); | ||||
|     assert_ne!(code, 403); | ||||
|  | ||||
|     let tenant_token = hashmap! { | ||||
|         "searchRules" => json!(["*"]), | ||||
|         "exp" => json!((Utc::now() + Duration::hours(1)).timestamp()) | ||||
|     }; | ||||
|  | ||||
|     let alt = generate_tenant_token(&key, tenant_token); | ||||
|     let altered_token = [ | ||||
|         web_token.split('.').next().unwrap(), | ||||
|         alt.split('.').nth(1).unwrap(), | ||||
|         web_token.split('.').nth(2).unwrap(), | ||||
|     ] | ||||
|     .join("."); | ||||
|  | ||||
|     server.use_api_key(&altered_token); | ||||
|     let (response, code) = server | ||||
|         .dummy_request("POST", "/indexes/products/search") | ||||
|         .await; | ||||
|     assert_eq!(response, INVALID_RESPONSE.clone()); | ||||
|     assert_eq!(code, 403); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user