mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-07-27 08:41:00 +00:00
Update authorization tests
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use ::time::format_description::well_known::Rfc3339;
|
use ::time::format_description::well_known::Rfc3339;
|
||||||
use maplit::{hashmap, hashset};
|
use maplit::{hashmap};
|
||||||
use meilisearch::Opt;
|
use meilisearch::Opt;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
@ -10,7 +10,30 @@ use time::{Duration, OffsetDateTime};
|
|||||||
use crate::common::{default_settings, Server, Value};
|
use crate::common::{default_settings, Server, Value};
|
||||||
use crate::json;
|
use crate::json;
|
||||||
|
|
||||||
pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'static str>>> =
|
macro_rules! hashset {
|
||||||
|
( $( $val:tt ),* $(,)? ) => {{
|
||||||
|
let mut set: HashSet<&'static [&'static str]> = HashSet::new();
|
||||||
|
$(
|
||||||
|
hashset!(@insert set, $val);
|
||||||
|
)*
|
||||||
|
set
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Match array-like input: ["a", "b"]
|
||||||
|
(@insert $set:ident, [ $($elem:literal),* ]) => {{
|
||||||
|
const ITEM: &[&str] = &[$($elem),*];
|
||||||
|
$set.insert(ITEM);
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Match single literal: "a"
|
||||||
|
(@insert $set:ident, $val:literal) => {{
|
||||||
|
const ITEM: &[&str] = &[$val];
|
||||||
|
$set.insert(ITEM);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'static [&'static str]>>> =
|
||||||
Lazy::new(|| {
|
Lazy::new(|| {
|
||||||
let authorizations = hashmap! {
|
let authorizations = hashmap! {
|
||||||
("POST", "/multi-search") => hashset!{"search", "*"},
|
("POST", "/multi-search") => hashset!{"search", "*"},
|
||||||
@ -23,6 +46,7 @@ pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'
|
|||||||
("DELETE", "/indexes/products/documents/0") => hashset!{"documents.delete", "documents.*", "*"},
|
("DELETE", "/indexes/products/documents/0") => hashset!{"documents.delete", "documents.*", "*"},
|
||||||
("POST", "/indexes/products/documents/delete-batch") => hashset!{"documents.delete", "documents.*", "*"},
|
("POST", "/indexes/products/documents/delete-batch") => hashset!{"documents.delete", "documents.*", "*"},
|
||||||
("POST", "/indexes/products/documents/delete") => hashset!{"documents.delete", "documents.*", "*"},
|
("POST", "/indexes/products/documents/delete") => hashset!{"documents.delete", "documents.*", "*"},
|
||||||
|
("POST", "/indexes/products/render") => hashset!{["settings.get", "documents.get"], ["documents.*", "settings.get"], ["settings.*", "documents.get"], "*"},
|
||||||
("GET", "/tasks") => hashset!{"tasks.get", "tasks.*", "*"},
|
("GET", "/tasks") => hashset!{"tasks.get", "tasks.*", "*"},
|
||||||
("DELETE", "/tasks") => hashset!{"tasks.delete", "tasks.*", "*"},
|
("DELETE", "/tasks") => hashset!{"tasks.delete", "tasks.*", "*"},
|
||||||
("GET", "/tasks?indexUid=products") => hashset!{"tasks.get", "tasks.*", "*"},
|
("GET", "/tasks?indexUid=products") => hashset!{"tasks.get", "tasks.*", "*"},
|
||||||
@ -76,7 +100,7 @@ pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'
|
|||||||
});
|
});
|
||||||
|
|
||||||
pub static ALL_ACTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
pub static ALL_ACTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||||
AUTHORIZATIONS.values().cloned().reduce(|l, r| l.union(&r).cloned().collect()).unwrap()
|
AUTHORIZATIONS.values().flat_map(|v| v.iter()).flat_map(|v| v.iter()).copied().collect::<HashSet<_>>()
|
||||||
});
|
});
|
||||||
|
|
||||||
static INVALID_RESPONSE: Lazy<Value> = Lazy::new(|| {
|
static INVALID_RESPONSE: Lazy<Value> = Lazy::new(|| {
|
||||||
@ -164,13 +188,14 @@ async fn error_access_unauthorized_index() {
|
|||||||
async fn error_access_unauthorized_action() {
|
async fn error_access_unauthorized_action() {
|
||||||
let mut server = Server::new_auth().await;
|
let mut server = Server::new_auth().await;
|
||||||
|
|
||||||
for ((method, route), action) in AUTHORIZATIONS.iter() {
|
for ((method, route), actions) in AUTHORIZATIONS.iter() {
|
||||||
// create a new API key letting only the needed action.
|
// create a new API key letting only the needed action.
|
||||||
server.use_api_key(MASTER_KEY);
|
server.use_api_key(MASTER_KEY);
|
||||||
|
|
||||||
|
let actions = actions.iter().flat_map(|s| s.iter()).copied().collect::<HashSet<_>>();
|
||||||
let content = json!({
|
let content = json!({
|
||||||
"indexes": ["products"],
|
"indexes": ["products"],
|
||||||
"actions": ALL_ACTIONS.difference(action).collect::<Vec<_>>(),
|
"actions": ALL_ACTIONS.difference(&actions).collect::<Vec<_>>(),
|
||||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -194,7 +219,7 @@ async fn access_authorized_master_key() {
|
|||||||
server.use_api_key(MASTER_KEY);
|
server.use_api_key(MASTER_KEY);
|
||||||
|
|
||||||
// master key must have access to all routes.
|
// master key must have access to all routes.
|
||||||
for ((method, route), _) in AUTHORIZATIONS.iter() {
|
for (method, route) in AUTHORIZATIONS.keys() {
|
||||||
let (response, code) = server.dummy_request(method, route).await;
|
let (response, code) = server.dummy_request(method, route).await;
|
||||||
|
|
||||||
assert_ne!(response, INVALID_RESPONSE.clone(), "on route: {:?} - {:?}", method, route);
|
assert_ne!(response, INVALID_RESPONSE.clone(), "on route: {:?} - {:?}", method, route);
|
||||||
@ -208,13 +233,13 @@ async fn access_authorized_restricted_index() {
|
|||||||
let enable_metrics = Opt { experimental_enable_metrics: true, ..default_settings(dir.path()) };
|
let enable_metrics = Opt { experimental_enable_metrics: true, ..default_settings(dir.path()) };
|
||||||
let mut server = Server::new_auth_with_options(enable_metrics, dir).await;
|
let mut server = Server::new_auth_with_options(enable_metrics, dir).await;
|
||||||
for ((method, route), actions) in AUTHORIZATIONS.iter() {
|
for ((method, route), actions) in AUTHORIZATIONS.iter() {
|
||||||
for action in actions {
|
for actions in actions {
|
||||||
// create a new API key letting only the needed action.
|
// create a new API key letting only the needed action.
|
||||||
server.use_api_key(MASTER_KEY);
|
server.use_api_key(MASTER_KEY);
|
||||||
|
|
||||||
let content = json!({
|
let content = json!({
|
||||||
"indexes": ["products"],
|
"indexes": ["products"],
|
||||||
"actions": [action],
|
"actions": actions,
|
||||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -232,20 +257,20 @@ async fn access_authorized_restricted_index() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
response,
|
response,
|
||||||
INVALID_METRICS_RESPONSE.clone(),
|
INVALID_METRICS_RESPONSE.clone(),
|
||||||
"on route: {:?} - {:?} with action: {:?}",
|
"on route: {:?} - {:?} with actions: {:?}",
|
||||||
method,
|
method,
|
||||||
route,
|
route,
|
||||||
action
|
actions
|
||||||
);
|
);
|
||||||
assert_eq!(code, 403);
|
assert_eq!(code, 403);
|
||||||
} else {
|
} else {
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
response,
|
response,
|
||||||
INVALID_RESPONSE.clone(),
|
INVALID_RESPONSE.clone(),
|
||||||
"on route: {:?} - {:?} with action: {:?}",
|
"on route: {:?} - {:?} with actions: {:?}",
|
||||||
method,
|
method,
|
||||||
route,
|
route,
|
||||||
action
|
actions
|
||||||
);
|
);
|
||||||
assert_ne!(code, 403);
|
assert_ne!(code, 403);
|
||||||
}
|
}
|
||||||
@ -253,18 +278,63 @@ async fn access_authorized_restricted_index() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn unauthorized_partial_actions() {
|
||||||
|
let mut server = Server::new_auth().await;
|
||||||
|
server.use_admin_key(MASTER_KEY).await;
|
||||||
|
|
||||||
|
// create index `products`
|
||||||
|
let index = server.index("products");
|
||||||
|
let (response, code) = index.create(Some("id")).await;
|
||||||
|
assert_eq!(202, code, "{:?}", &response);
|
||||||
|
let task_id = response["taskUid"].as_u64().unwrap();
|
||||||
|
index.wait_task(task_id).await.succeeded();
|
||||||
|
|
||||||
|
// When multiple actions are necessary, the server mustn't accept any combination with one action missing.
|
||||||
|
for ((method, route), actions) in AUTHORIZATIONS.iter() {
|
||||||
|
for actions in actions {
|
||||||
|
if 2 <= actions.len() {
|
||||||
|
for excluded_action in *actions {
|
||||||
|
// create a new API key letting all actions except one.
|
||||||
|
server.use_api_key(MASTER_KEY);
|
||||||
|
|
||||||
|
let actions = actions.iter().filter(|&a| a != excluded_action).copied().collect::<HashSet<_>>();
|
||||||
|
let content = json!({
|
||||||
|
"indexes": ["products"],
|
||||||
|
"actions": actions,
|
||||||
|
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let (response, code) = server.add_api_key(content).await;
|
||||||
|
assert_eq!(201, code, "{:?}", &response);
|
||||||
|
assert!(response["key"].is_string());
|
||||||
|
|
||||||
|
let key = response["key"].as_str().unwrap();
|
||||||
|
server.use_api_key(key);
|
||||||
|
|
||||||
|
let (mut response, code) = server.dummy_request(method, route).await;
|
||||||
|
response["message"] = serde_json::json!(null);
|
||||||
|
|
||||||
|
assert_eq!(response, INVALID_RESPONSE.clone(), "on route: {:?} - {:?} with actions: {:?}", method, route, actions);
|
||||||
|
assert_eq!(code, 403, "{:?}", &response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn access_authorized_no_index_restriction() {
|
async fn access_authorized_no_index_restriction() {
|
||||||
let mut server = Server::new_auth().await;
|
let mut server = Server::new_auth().await;
|
||||||
|
|
||||||
for ((method, route), actions) in AUTHORIZATIONS.iter() {
|
for ((method, route), actions) in AUTHORIZATIONS.iter() {
|
||||||
for action in actions {
|
for actions in actions {
|
||||||
// create a new API key letting only the needed action.
|
// create a new API key letting only the needed action.
|
||||||
server.use_api_key(MASTER_KEY);
|
server.use_api_key(MASTER_KEY);
|
||||||
|
|
||||||
let content = json!({
|
let content = json!({
|
||||||
"indexes": ["*"],
|
"indexes": ["*"],
|
||||||
"actions": [action],
|
"actions": actions,
|
||||||
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
"expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -280,12 +350,12 @@ async fn access_authorized_no_index_restriction() {
|
|||||||
assert_ne!(
|
assert_ne!(
|
||||||
response,
|
response,
|
||||||
INVALID_RESPONSE.clone(),
|
INVALID_RESPONSE.clone(),
|
||||||
"on route: {:?} - {:?} with action: {:?}",
|
"on route: {:?} - {:?} with actions: {:?}",
|
||||||
method,
|
method,
|
||||||
route,
|
route,
|
||||||
action
|
actions
|
||||||
);
|
);
|
||||||
assert_ne!(code, 403, "on route: {:?} - {:?} with action: {:?}", method, route, action);
|
assert_ne!(code, 403, "on route: {:?} - {:?} with action: {:?}", method, route, actions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -723,10 +793,11 @@ async fn error_creating_index_without_action() {
|
|||||||
server.use_api_key(MASTER_KEY);
|
server.use_api_key(MASTER_KEY);
|
||||||
|
|
||||||
// create key with access on all indexes.
|
// create key with access on all indexes.
|
||||||
|
let create_index_actions = AUTHORIZATIONS.get(&("POST","/indexes")).unwrap().iter().flat_map(|s| s.iter()).cloned().collect::<HashSet<_>>();
|
||||||
let content = json!({
|
let content = json!({
|
||||||
"indexes": ["*"],
|
"indexes": ["*"],
|
||||||
// Give all action but the ones allowing to create an index.
|
// Give all action but the ones allowing to create an index.
|
||||||
"actions": ALL_ACTIONS.iter().cloned().filter(|a| !AUTHORIZATIONS.get(&("POST","/indexes")).unwrap().contains(a)).collect::<Vec<_>>(),
|
"actions": ALL_ACTIONS.iter().cloned().filter(|a| !create_index_actions.contains(a)).collect::<Vec<_>>(),
|
||||||
"expiresAt": "2050-11-13T00:00:00Z"
|
"expiresAt": "2050-11-13T00:00:00Z"
|
||||||
});
|
});
|
||||||
let (response, code) = server.add_api_key(content).await;
|
let (response, code) = server.add_api_key(content).await;
|
||||||
|
Reference in New Issue
Block a user