Add indexUid filtering on the /tasks route

This commit is contained in:
Kerollmops 2022-05-17 16:08:23 +02:00
parent 80f7d87356
commit 3684c822f1
No known key found for this signature in database
GPG Key ID: 92ADA4E935E71FA4
11 changed files with 106 additions and 53 deletions

10
Cargo.lock generated
View File

@ -2046,6 +2046,7 @@ dependencies = [
"rustls-pemfile", "rustls-pemfile",
"segment", "segment",
"serde", "serde",
"serde-cs",
"serde_json", "serde_json",
"serde_url_params", "serde_url_params",
"sha-1", "sha-1",
@ -3085,6 +3086,15 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-cs"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d5b0435c9139761fbe5abeb1283234bcfbde88fadc2ae432579648fbce72ad"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.136" version = "1.0.136"

View File

@ -62,6 +62,7 @@ rustls = "0.20.4"
rustls-pemfile = "0.3.0" rustls-pemfile = "0.3.0"
segment = { version = "0.2.0", optional = true } segment = { version = "0.2.0", optional = true }
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
serde-cs = "0.2.2"
serde_json = { version = "1.0.79", features = ["preserve_order"] } serde_json = { version = "1.0.79", features = ["preserve_order"] }
sha2 = "0.10.2" sha2 = "0.10.2"
siphasher = "0.3.10" siphasher = "0.3.10"

View File

@ -2,7 +2,7 @@
#[macro_use] #[macro_use]
pub mod error; pub mod error;
pub mod analytics; pub mod analytics;
mod task; pub mod task;
#[macro_use] #[macro_use]
pub mod extractors; pub mod extractors;
pub mod helpers; pub mod helpers;

View File

@ -2,21 +2,33 @@ use actix_web::{web, HttpRequest, HttpResponse};
use meilisearch_error::ResponseError; use meilisearch_error::ResponseError;
use meilisearch_lib::tasks::task::TaskId; use meilisearch_lib::tasks::task::TaskId;
use meilisearch_lib::tasks::TaskFilter; use meilisearch_lib::tasks::TaskFilter;
use meilisearch_lib::MeiliSearch; use meilisearch_lib::{IndexUid, MeiliSearch};
use serde::Deserialize;
use serde_cs::vec::CS;
use serde_json::json; use serde_json::json;
use crate::analytics::Analytics; use crate::analytics::Analytics;
use crate::extractors::authentication::{policies::*, GuardedData}; use crate::extractors::authentication::{policies::*, GuardedData};
use crate::extractors::sequential_extractor::SeqHandler; use crate::extractors::sequential_extractor::SeqHandler;
use crate::task::{TaskListView, TaskView}; use crate::task::{TaskListView, TaskStatus, TaskType, TaskView};
pub fn configure(cfg: &mut web::ServiceConfig) { pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(web::resource("").route(web::get().to(SeqHandler(get_tasks)))) cfg.service(web::resource("").route(web::get().to(SeqHandler(get_tasks))))
.service(web::resource("/{task_id}").route(web::get().to(SeqHandler(get_task)))); .service(web::resource("/{task_id}").route(web::get().to(SeqHandler(get_task))));
} }
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TasksFilter {
#[serde(rename = "type")]
type_: Option<CS<TaskType>>,
status: Option<CS<TaskStatus>>,
index_uid: Option<CS<IndexUid>>,
}
async fn get_tasks( async fn get_tasks(
meilisearch: GuardedData<ActionPolicy<{ actions::TASKS_GET }>, MeiliSearch>, meilisearch: GuardedData<ActionPolicy<{ actions::TASKS_GET }>, MeiliSearch>,
params: web::Query<TasksFilter>,
req: HttpRequest, req: HttpRequest,
analytics: web::Data<dyn Analytics>, analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
@ -26,15 +38,34 @@ async fn get_tasks(
Some(&req), Some(&req),
); );
let TasksFilter {
type_,
status,
index_uid,
} = params.into_inner();
let search_rules = &meilisearch.filters().search_rules; let search_rules = &meilisearch.filters().search_rules;
let filters = if search_rules.is_index_authorized("*") { let filters = match index_uid {
None Some(indexes) => {
} else { let mut filters = TaskFilter::default();
let mut filters = TaskFilter::default(); for name in indexes.into_inner() {
for (index, _policy) in search_rules.clone() { if search_rules.is_index_authorized(&name) {
filters.filter_index(index); filters.filter_index(name.to_string());
}
}
Some(filters)
}
None => {
if search_rules.is_index_authorized("*") {
None
} else {
let mut filters = TaskFilter::default();
for (index, _policy) in search_rules.clone() {
filters.filter_index(index);
}
Some(filters)
}
} }
Some(filters)
}; };
let tasks: TaskListView = meilisearch let tasks: TaskListView = meilisearch

View File

@ -1,4 +1,5 @@
use std::fmt::Write; use std::fmt::Write;
use std::str::FromStr;
use std::write; use std::write;
use meilisearch_error::ResponseError; use meilisearch_error::ResponseError;
@ -8,14 +9,14 @@ use meilisearch_lib::tasks::batch::BatchId;
use meilisearch_lib::tasks::task::{ use meilisearch_lib::tasks::task::{
DocumentDeletion, Task, TaskContent, TaskEvent, TaskId, TaskResult, DocumentDeletion, Task, TaskContent, TaskEvent, TaskId, TaskResult,
}; };
use serde::{Serialize, Serializer}; use serde::{Deserialize, Serialize, Serializer};
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
use crate::AUTOBATCHING_ENABLED; use crate::AUTOBATCHING_ENABLED;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
enum TaskType { pub enum TaskType {
IndexCreation, IndexCreation,
IndexUpdate, IndexUpdate,
IndexDeletion, IndexDeletion,
@ -50,15 +51,47 @@ impl From<TaskContent> for TaskType {
} }
} }
#[derive(Debug, Serialize)] impl FromStr for TaskType {
type Err = &'static str;
fn from_str(status: &str) -> Result<Self, &'static str> {
match status {
"indexCreation" => Ok(TaskType::IndexCreation),
"indexUpdate" => Ok(TaskType::IndexUpdate),
"indexDeletion" => Ok(TaskType::IndexDeletion),
"documentAddition" => Ok(TaskType::DocumentAddition),
"documentPartial" => Ok(TaskType::DocumentPartial),
"documentDeletion" => Ok(TaskType::DocumentDeletion),
"settingsUpdate" => Ok(TaskType::SettingsUpdate),
"clearAll" => Ok(TaskType::ClearAll),
_ => Err("invalid task type value"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
enum TaskStatus { pub enum TaskStatus {
Enqueued, Enqueued,
Processing, Processing,
Succeeded, Succeeded,
Failed, Failed,
} }
impl FromStr for TaskStatus {
type Err = &'static str;
fn from_str(status: &str) -> Result<Self, &'static str> {
match status {
"enqueued" => Ok(TaskStatus::Enqueued),
"processing" => Ok(TaskStatus::Processing),
"succeeded" => Ok(TaskStatus::Succeeded),
"failed" => Ok(TaskStatus::Failed),
_ => Err("invalid task status value"),
}
}
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(untagged)] #[serde(untagged)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]

View File

@ -16,8 +16,8 @@ pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'
("GET", "/indexes/products/documents/0") => hashset!{"documents.get", "*"}, ("GET", "/indexes/products/documents/0") => hashset!{"documents.get", "*"},
("DELETE", "/indexes/products/documents/0") => hashset!{"documents.delete", "*"}, ("DELETE", "/indexes/products/documents/0") => hashset!{"documents.delete", "*"},
("GET", "/tasks") => hashset!{"tasks.get", "*"}, ("GET", "/tasks") => hashset!{"tasks.get", "*"},
("GET", "/indexes/products/tasks") => hashset!{"tasks.get", "*"}, ("GET", "/tasks?indexUid=products") => hashset!{"tasks.get", "*"},
("GET", "/indexes/products/tasks/0") => hashset!{"tasks.get", "*"}, ("GET", "/tasks/0") => hashset!{"tasks.get", "*"},
("PUT", "/indexes/products/") => hashset!{"indexes.update", "*"}, ("PUT", "/indexes/products/") => hashset!{"indexes.update", "*"},
("GET", "/indexes/products/") => hashset!{"indexes.get", "*"}, ("GET", "/indexes/products/") => hashset!{"indexes.get", "*"},
("DELETE", "/indexes/products/") => hashset!{"indexes.delete", "*"}, ("DELETE", "/indexes/products/") => hashset!{"indexes.delete", "*"},

View File

@ -122,12 +122,12 @@ impl Index<'_> {
} }
pub async fn get_task(&self, update_id: u64) -> (Value, StatusCode) { pub async fn get_task(&self, update_id: u64) -> (Value, StatusCode) {
let url = format!("/indexes/{}/tasks/{}", self.uid, update_id); let url = format!("/tasks/{}", update_id);
self.service.get(url).await self.service.get(url).await
} }
pub async fn list_tasks(&self) -> (Value, StatusCode) { pub async fn list_tasks(&self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/tasks", self.uid); let url = format!("/tasks?indexUid={}", self.uid);
self.service.get(url).await self.service.get(url).await
} }

View File

@ -3,22 +3,6 @@ use serde_json::json;
use time::format_description::well_known::Rfc3339; use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime; use time::OffsetDateTime;
#[actix_rt::test]
async fn error_get_task_unexisting_index() {
let server = Server::new().await;
let (response, code) = server.service.get("/indexes/test/tasks").await;
let expected_response = json!({
"message": "Index `test` not found.",
"code": "index_not_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#index_not_found"
});
assert_eq!(response, expected_response);
assert_eq!(code, 404);
}
#[actix_rt::test] #[actix_rt::test]
async fn error_get_unexisting_task_status() { async fn error_get_unexisting_task_status() {
let server = Server::new().await; let server = Server::new().await;
@ -58,22 +42,6 @@ async fn get_task_status() {
// TODO check resonse format, as per #48 // TODO check resonse format, as per #48
} }
#[actix_rt::test]
async fn error_list_tasks_unexisting_index() {
let server = Server::new().await;
let (response, code) = server.index("test").list_tasks().await;
let expected_response = json!({
"message": "Index `test` not found.",
"code": "index_not_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#index_not_found"
});
assert_eq!(response, expected_response);
assert_eq!(code, 404);
}
#[actix_rt::test] #[actix_rt::test]
async fn list_tasks() { async fn list_tasks() {
let server = Server::new().await; let server = Server::new().await;

View File

@ -35,7 +35,8 @@ use error::Result;
use self::error::IndexControllerError; use self::error::IndexControllerError;
use crate::index_resolver::index_store::{IndexStore, MapIndexStore}; use crate::index_resolver::index_store::{IndexStore, MapIndexStore};
use crate::index_resolver::meta_store::{HeedMetaStore, IndexMetaStore}; use crate::index_resolver::meta_store::{HeedMetaStore, IndexMetaStore};
use crate::index_resolver::{create_index_resolver, IndexResolver, IndexUid}; pub use crate::index_resolver::IndexUid;
use crate::index_resolver::{create_index_resolver, IndexResolver};
use crate::update_file_store::UpdateFileStore; use crate::update_file_store::UpdateFileStore;
pub mod error; pub mod error;

View File

@ -4,6 +4,7 @@ pub mod meta_store;
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::path::Path; use std::path::Path;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use error::{IndexResolverError, Result}; use error::{IndexResolverError, Result};
@ -88,6 +89,14 @@ impl TryInto<IndexUid> for String {
} }
} }
impl FromStr for IndexUid {
type Err = IndexResolverError;
fn from_str(s: &str) -> Result<IndexUid> {
IndexUid::new(s.to_string())
}
}
pub struct IndexResolver<U, I> { pub struct IndexResolver<U, I> {
index_uuid_store: U, index_uuid_store: U,
index_store: I, index_store: I,

View File

@ -13,7 +13,7 @@ mod update_file_store;
use std::path::Path; use std::path::Path;
pub use index_controller::MeiliSearch; pub use index_controller::{IndexUid, MeiliSearch};
pub use milli; pub use milli;
pub use milli::heed; pub use milli::heed;