From 5229f1e220b27eef72a4084c76e1dc502729ccc4 Mon Sep 17 00:00:00 2001 From: marin postma Date: Tue, 22 Jun 2021 23:49:34 +0200 Subject: [PATCH 01/18] experimental auth extractor --- meilisearch-http/src/routes/search.rs | 127 ++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 9 deletions(-) diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index cc1306e71..2fbbfe4b3 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -1,18 +1,129 @@ -use std::collections::{BTreeSet, HashSet}; +use std::any::{Any, TypeId}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::marker::PhantomData; +use std::ops::Deref; -use actix_web::{get, post, web, HttpResponse}; use log::debug; +use actix_web::{web, FromRequest, HttpResponse}; +use futures::future::{err, ok, Ready}; use serde::Deserialize; use serde_json::Value; -use crate::error::ResponseError; -use crate::helpers::Authentication; +use crate::error::{AuthenticationError, ResponseError}; use crate::index::{default_crop_length, SearchQuery, DEFAULT_SEARCH_LIMIT}; use crate::routes::IndexParam; use crate::Data; +struct Public; + +impl Policy for Public { + fn authenticate(&self, _token: &[u8]) -> bool { + true + } +} + +struct GuardedData { + data: D, + _marker: PhantomData, +} + +trait Policy { + fn authenticate(&self, token: &[u8]) -> bool; +} + +struct Policies { + inner: HashMap>, +} + +impl Policies { + fn new() -> Self { + Self { inner: HashMap::new() } + } + + fn insert(&mut self, policy: S) { + self.inner.insert(TypeId::of::(), Box::new(policy)); + } + + fn get(&self) -> Option<&S> { + self.inner + .get(&TypeId::of::()) + .and_then(|p| p.downcast_ref::()) + } +} + +enum AuthConfig { + NoAuth, + Auth(Policies), +} + +impl Default for AuthConfig { + fn default() -> Self { + Self::NoAuth + } +} + +impl FromRequest for GuardedData { + type Config = AuthConfig; + + type Error = ResponseError; + + type Future = Ready>; + + fn from_request( + req: &actix_web::HttpRequest, + _payload: &mut actix_http::Payload, + ) -> Self::Future { + match req.app_data::() { + Some(config) => match config { + AuthConfig::NoAuth => match req.app_data::().cloned() { + Some(data) => ok(Self { + data, + _marker: PhantomData, + }), + None => todo!("Data not configured"), + }, + AuthConfig::Auth(policies) => match policies.get::

() { + Some(policy) => match req.headers().get("x-meili-api-key") { + Some(token) => { + if policy.authenticate(token.as_bytes()) { + match req.app_data::().cloned() { + Some(data) => ok(Self { + data, + _marker: PhantomData, + }), + None => todo!("Data not configured"), + } + } else { + err(AuthenticationError::InvalidToken(String::from("hello")).into()) + } + } + None => err(AuthenticationError::MissingAuthorizationHeader.into()), + }, + None => todo!("no policy found"), + }, + }, + None => todo!(), + } + } +} + +impl Deref for GuardedData { + type Target = D; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(search_with_post).service(search_with_url_query); + let mut policies = Policies::new(); + policies.insert(Public); + cfg.service( + web::resource("/indexes/{index_uid}/search") + .app_data(AuthConfig::Auth(policies)) + .route(web::get().to(search_with_url_query)) + .route(web::post().to(search_with_post)), + ); } #[derive(Deserialize, Debug)] @@ -73,9 +184,8 @@ impl From for SearchQuery { } } -#[get("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_url_query( - data: web::Data, + data: GuardedData, path: web::Path, params: web::Query, ) -> Result { @@ -86,9 +196,8 @@ async fn search_with_url_query( Ok(HttpResponse::Ok().json(search_result)) } -#[post("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_post( - data: web::Data, + data: GuardedData, path: web::Path, params: web::Json, ) -> Result { From 12f6709e1c61c2960c8f5d8d6e2813017be5af2f Mon Sep 17 00:00:00 2001 From: marin postma Date: Wed, 23 Jun 2021 18:54:33 +0200 Subject: [PATCH 02/18] move authencation to extractor mod --- .../src/extractors/authentication/mod.rs | 117 ++++++++++++++++++ meilisearch-http/src/extractors/mod.rs | 1 + meilisearch-http/src/routes/search.rs | 112 +---------------- 3 files changed, 122 insertions(+), 108 deletions(-) create mode 100644 meilisearch-http/src/extractors/authentication/mod.rs diff --git a/meilisearch-http/src/extractors/authentication/mod.rs b/meilisearch-http/src/extractors/authentication/mod.rs new file mode 100644 index 000000000..a31847945 --- /dev/null +++ b/meilisearch-http/src/extractors/authentication/mod.rs @@ -0,0 +1,117 @@ +use std::collections::HashMap; +use std::marker::PhantomData; +use std::ops::Deref; +use std::any::{Any, TypeId}; + +use actix_web::FromRequest; +use futures::future::err; +use futures::future::{Ready, ok}; + +use crate::error::{AuthenticationError, ResponseError}; + +pub struct Public; + +impl Policy for Public { + fn authenticate(&self, _token: &[u8]) -> bool { + true + } +} + +pub struct GuardedData { + data: D, + _marker: PhantomData, +} + +impl Deref for GuardedData { + type Target = D; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +pub trait Policy { + fn authenticate(&self, token: &[u8]) -> bool; +} + +pub struct Policies { + inner: HashMap>, +} + +impl Policies { + pub fn new() -> Self { + Self { inner: HashMap::new() } + } + + pub fn insert(&mut self, policy: S) { + self.inner.insert(TypeId::of::(), Box::new(policy)); + } + + pub fn get(&self) -> Option<&S> { + self.inner + .get(&TypeId::of::()) + .and_then(|p| p.downcast_ref::()) + } +} + +impl Default for Policies { + fn default() -> Self { + Self::new() + } +} + +pub enum AuthConfig { + NoAuth, + Auth(Policies), +} + +impl Default for AuthConfig { + fn default() -> Self { + Self::NoAuth + } +} + +impl FromRequest for GuardedData { + type Config = AuthConfig; + + type Error = ResponseError; + + type Future = Ready>; + + fn from_request( + req: &actix_web::HttpRequest, + _payload: &mut actix_http::Payload, + ) -> Self::Future { + match req.app_data::() { + Some(config) => match config { + AuthConfig::NoAuth => match req.app_data::().cloned() { + Some(data) => ok(Self { + data, + _marker: PhantomData, + }), + None => todo!("Data not configured"), + }, + AuthConfig::Auth(policies) => match policies.get::

() { + Some(policy) => match req.headers().get("x-meili-api-key") { + Some(token) => { + if policy.authenticate(token.as_bytes()) { + match req.app_data::().cloned() { + Some(data) => ok(Self { + data, + _marker: PhantomData, + }), + None => todo!("Data not configured"), + } + } else { + err(AuthenticationError::InvalidToken(String::from("hello")).into()) + } + } + None => err(AuthenticationError::MissingAuthorizationHeader.into()), + }, + None => todo!("no policy found"), + }, + }, + None => todo!(), + } + } +} diff --git a/meilisearch-http/src/extractors/mod.rs b/meilisearch-http/src/extractors/mod.rs index fbb091fe2..8d2942f1d 100644 --- a/meilisearch-http/src/extractors/mod.rs +++ b/meilisearch-http/src/extractors/mod.rs @@ -1 +1,2 @@ pub mod payload; +pub mod authentication; diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 2fbbfe4b3..0d3184ca9 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -1,119 +1,15 @@ -use std::any::{Any, TypeId}; -use std::collections::{BTreeSet, HashMap, HashSet}; -use std::marker::PhantomData; -use std::ops::Deref; +use std::collections::{BTreeSet, HashSet}; use log::debug; -use actix_web::{web, FromRequest, HttpResponse}; -use futures::future::{err, ok, Ready}; +use actix_web::{web, HttpResponse}; use serde::Deserialize; use serde_json::Value; -use crate::error::{AuthenticationError, ResponseError}; +use crate::error::ResponseError; use crate::index::{default_crop_length, SearchQuery, DEFAULT_SEARCH_LIMIT}; use crate::routes::IndexParam; use crate::Data; - -struct Public; - -impl Policy for Public { - fn authenticate(&self, _token: &[u8]) -> bool { - true - } -} - -struct GuardedData { - data: D, - _marker: PhantomData, -} - -trait Policy { - fn authenticate(&self, token: &[u8]) -> bool; -} - -struct Policies { - inner: HashMap>, -} - -impl Policies { - fn new() -> Self { - Self { inner: HashMap::new() } - } - - fn insert(&mut self, policy: S) { - self.inner.insert(TypeId::of::(), Box::new(policy)); - } - - fn get(&self) -> Option<&S> { - self.inner - .get(&TypeId::of::()) - .and_then(|p| p.downcast_ref::()) - } -} - -enum AuthConfig { - NoAuth, - Auth(Policies), -} - -impl Default for AuthConfig { - fn default() -> Self { - Self::NoAuth - } -} - -impl FromRequest for GuardedData { - type Config = AuthConfig; - - type Error = ResponseError; - - type Future = Ready>; - - fn from_request( - req: &actix_web::HttpRequest, - _payload: &mut actix_http::Payload, - ) -> Self::Future { - match req.app_data::() { - Some(config) => match config { - AuthConfig::NoAuth => match req.app_data::().cloned() { - Some(data) => ok(Self { - data, - _marker: PhantomData, - }), - None => todo!("Data not configured"), - }, - AuthConfig::Auth(policies) => match policies.get::

() { - Some(policy) => match req.headers().get("x-meili-api-key") { - Some(token) => { - if policy.authenticate(token.as_bytes()) { - match req.app_data::().cloned() { - Some(data) => ok(Self { - data, - _marker: PhantomData, - }), - None => todo!("Data not configured"), - } - } else { - err(AuthenticationError::InvalidToken(String::from("hello")).into()) - } - } - None => err(AuthenticationError::MissingAuthorizationHeader.into()), - }, - None => todo!("no policy found"), - }, - }, - None => todo!(), - } - } -} - -impl Deref for GuardedData { - type Target = D; - - fn deref(&self) -> &Self::Target { - &self.data - } -} +use crate::extractors::authentication::{Policies, AuthConfig, Public, GuardedData}; pub fn services(cfg: &mut web::ServiceConfig) { let mut policies = Policies::new(); From 5b717513916f8b201af37b960b1b50f6a13567b4 Mon Sep 17 00:00:00 2001 From: marin postma Date: Wed, 23 Jun 2021 19:35:26 +0200 Subject: [PATCH 03/18] policies macros --- .../src/extractors/authentication/mod.rs | 64 +++++++++++++++++-- meilisearch-http/src/extractors/mod.rs | 1 + meilisearch-http/src/lib.rs | 4 ++ meilisearch-http/src/routes/search.rs | 2 +- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/meilisearch-http/src/extractors/authentication/mod.rs b/meilisearch-http/src/extractors/authentication/mod.rs index a31847945..315461578 100644 --- a/meilisearch-http/src/extractors/authentication/mod.rs +++ b/meilisearch-http/src/extractors/authentication/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::marker::PhantomData; use std::ops::Deref; use std::any::{Any, TypeId}; @@ -9,12 +9,59 @@ use futures::future::{Ready, ok}; use crate::error::{AuthenticationError, ResponseError}; -pub struct Public; +macro_rules! create_policies { + ($($name:ident), *) => { + $( + pub struct $name { + inner: HashSet> + } -impl Policy for Public { - fn authenticate(&self, _token: &[u8]) -> bool { - true - } + impl $name { + pub fn new() -> Self { + Self { inner: HashSet::new() } + } + + pub fn add(&mut self, token: Vec) { + self.inner.insert(token); + } + } + + impl Policy for $name { + fn authenticate(&self, token: &[u8]) -> bool { + self.inner.contains(token) + } + } + )* + }; +} + +create_policies!(Public, Private, Admin); + +/// Instanciate a `Policies`, filled with the given policies. +macro_rules! init_policies { + ($($name:ident), *) => { + { + let mut policies = Policies::new(); + $( + let policy = $name::new(); + policies.insert(policy); + )* + policies + } + }; +} + +/// Adds user to all specified policies. +macro_rules! create_users { + ($policies:ident, $($user:literal => { $($policy:ty), * }), *) => { + { + $( + $( + $policies.get_mut::<$policy>().map(|p| p.add($user.to_owned())) + )* + )* + } + }; } pub struct GuardedData { @@ -52,6 +99,11 @@ impl Policies { .get(&TypeId::of::()) .and_then(|p| p.downcast_ref::()) } + + pub fn get_mut(&mut self) -> Option<&mut S> { + self.inner.get_mut(&TypeId::of::()) + .and_then(|p| p.downcast_mut::()) + } } impl Default for Policies { diff --git a/meilisearch-http/src/extractors/mod.rs b/meilisearch-http/src/extractors/mod.rs index 8d2942f1d..09a56e4a0 100644 --- a/meilisearch-http/src/extractors/mod.rs +++ b/meilisearch-http/src/extractors/mod.rs @@ -1,2 +1,3 @@ pub mod payload; +#[macro_use] pub mod authentication; diff --git a/meilisearch-http/src/lib.rs b/meilisearch-http/src/lib.rs index 46fea718c..0ee1fb4c2 100644 --- a/meilisearch-http/src/lib.rs +++ b/meilisearch-http/src/lib.rs @@ -35,6 +35,10 @@ pub fn configure_data(config: &mut web::ServiceConfig, data: Data) { ); } +pub fn configure_auth(config: &mut web::ServiceConfig, opt: &Options) { + todo!() +} + #[cfg(feature = "mini-dashboard")] pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) { use actix_web_static_files::Resource; diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 0d3184ca9..160660daf 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -13,7 +13,7 @@ use crate::extractors::authentication::{Policies, AuthConfig, Public, GuardedDat pub fn services(cfg: &mut web::ServiceConfig) { let mut policies = Policies::new(); - policies.insert(Public); + policies.insert(Public::new()); cfg.service( web::resource("/indexes/{index_uid}/search") .app_data(AuthConfig::Auth(policies)) From 0c1c7a3dd9085fb2d7a4708b19a582fa59f82d3b Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 14:22:12 +0200 Subject: [PATCH 04/18] implement authentication policies --- .../src/extractors/authentication/mod.rs | 58 +++++++++++-------- meilisearch-http/src/lib.rs | 30 ++++++++-- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/meilisearch-http/src/extractors/authentication/mod.rs b/meilisearch-http/src/extractors/authentication/mod.rs index 315461578..2cce6911d 100644 --- a/meilisearch-http/src/extractors/authentication/mod.rs +++ b/meilisearch-http/src/extractors/authentication/mod.rs @@ -1,37 +1,43 @@ -use std::collections::{HashMap, HashSet}; +use std::any::{Any, TypeId}; +use std::collections::HashMap; use std::marker::PhantomData; use std::ops::Deref; -use std::any::{Any, TypeId}; use actix_web::FromRequest; use futures::future::err; -use futures::future::{Ready, ok}; +use futures::future::{ok, Ready}; use crate::error::{AuthenticationError, ResponseError}; macro_rules! create_policies { ($($name:ident), *) => { - $( - pub struct $name { - inner: HashSet> - } + pub mod policies { + use std::collections::HashSet; + use crate::extractors::authentication::Policy; - impl $name { - pub fn new() -> Self { - Self { inner: HashSet::new() } + $( + #[derive(Debug)] + pub struct $name { + inner: HashSet> } - pub fn add(&mut self, token: Vec) { - self.inner.insert(token); - } - } + impl $name { + pub fn new() -> Self { + Self { inner: HashSet::new() } + } - impl Policy for $name { - fn authenticate(&self, token: &[u8]) -> bool { - self.inner.contains(token) + pub fn add(&mut self, token: Vec) { + &mut self.inner.insert(token); + } } - } - )* + + impl Policy for $name { + fn authenticate(&self, token: &[u8]) -> bool { + self.inner.contains(token) + } + } + )* + } }; } @@ -41,7 +47,7 @@ create_policies!(Public, Private, Admin); macro_rules! init_policies { ($($name:ident), *) => { { - let mut policies = Policies::new(); + let mut policies = crate::extractors::authentication::Policies::new(); $( let policy = $name::new(); policies.insert(policy); @@ -53,11 +59,11 @@ macro_rules! init_policies { /// Adds user to all specified policies. macro_rules! create_users { - ($policies:ident, $($user:literal => { $($policy:ty), * }), *) => { + ($policies:ident, $($user:expr => { $($policy:ty), * }), *) => { { $( $( - $policies.get_mut::<$policy>().map(|p| p.add($user.to_owned())) + $policies.get_mut::<$policy>().map(|p| p.add($user.to_owned())); )* )* } @@ -81,13 +87,16 @@ pub trait Policy { fn authenticate(&self, token: &[u8]) -> bool; } +#[derive(Debug)] pub struct Policies { inner: HashMap>, } impl Policies { pub fn new() -> Self { - Self { inner: HashMap::new() } + Self { + inner: HashMap::new(), + } } pub fn insert(&mut self, policy: S) { @@ -101,7 +110,8 @@ impl Policies { } pub fn get_mut(&mut self) -> Option<&mut S> { - self.inner.get_mut(&TypeId::of::()) + self.inner + .get_mut(&TypeId::of::()) .and_then(|p| p.downcast_mut::()) } } diff --git a/meilisearch-http/src/lib.rs b/meilisearch-http/src/lib.rs index 0ee1fb4c2..7b9dbdc21 100644 --- a/meilisearch-http/src/lib.rs +++ b/meilisearch-http/src/lib.rs @@ -1,6 +1,7 @@ pub mod data; #[macro_use] pub mod error; +#[macro_use] pub mod extractors; pub mod helpers; mod index; @@ -11,17 +12,21 @@ pub mod routes; #[cfg(all(not(debug_assertions), feature = "analytics"))] pub mod analytics; +use crate::extractors::authentication::AuthConfig; + pub use self::data::Data; pub use option::Opt; use actix_web::web; use extractors::payload::PayloadConfig; +use extractors::authentication::policies::*; pub fn configure_data(config: &mut web::ServiceConfig, data: Data) { let http_payload_size_limit = data.http_payload_size_limit(); config - .data(data) + .data(data.clone()) + .app_data(data) .app_data( web::JsonConfig::default() .limit(http_payload_size_limit) @@ -35,8 +40,24 @@ pub fn configure_data(config: &mut web::ServiceConfig, data: Data) { ); } -pub fn configure_auth(config: &mut web::ServiceConfig, opt: &Options) { - todo!() +pub fn configure_auth(config: &mut web::ServiceConfig, data: &Data) { + let keys = data.api_keys(); + let auth_config = if let Some(ref master_key) = keys.master { + let private_key = keys.private.as_ref().unwrap(); + let public_key = keys.public.as_ref().unwrap(); + let mut policies = init_policies!(Public, Private, Admin); + create_users!( + policies, + master_key.as_bytes() => { Admin, Private, Public }, + private_key.as_bytes() => { Private, Public }, + public_key.as_bytes() => { Public } + ); + AuthConfig::Auth(policies) + } else { + AuthConfig::NoAuth + }; + + config.app_data(auth_config); } #[cfg(feature = "mini-dashboard")] @@ -84,10 +105,11 @@ macro_rules! create_app { use actix_web::App; use actix_web::{middleware, web}; use meilisearch_http::routes::*; - use meilisearch_http::{configure_data, dashboard}; + use meilisearch_http::{configure_data, dashboard, configure_auth}; App::new() .configure(|s| configure_data(s, $data.clone())) + .configure(|s| configure_auth(s, &$data)) .configure(document::services) .configure(index::services) .configure(search::services) From adf91d286b99ba25935b00a68097d9efb3c90802 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:02:35 +0200 Subject: [PATCH 05/18] update documents and search routes --- meilisearch-http/src/routes/document.rs | 87 +++++++------------------ meilisearch-http/src/routes/search.rs | 7 +- 2 files changed, 27 insertions(+), 67 deletions(-) diff --git a/meilisearch-http/src/routes/document.rs b/meilisearch-http/src/routes/document.rs index 6ac521f79..4824ec7b8 100644 --- a/meilisearch-http/src/routes/document.rs +++ b/meilisearch-http/src/routes/document.rs @@ -1,14 +1,12 @@ -use actix_web::{delete, get, post, put}; use actix_web::{web, HttpResponse}; -use indexmap::IndexMap; -use log::{debug, error}; +use log::debug; use milli::update::{IndexDocumentsMethod, UpdateFormat}; use serde::Deserialize; use serde_json::Value; use crate::error::ResponseError; +use crate::extractors::authentication::{policies::*, GuardedData}; use crate::extractors::payload::Payload; -use crate::helpers::Authentication; use crate::routes::IndexParam; use crate::Data; @@ -17,7 +15,6 @@ const DEFAULT_RETRIEVE_DOCUMENTS_LIMIT: usize = 20; macro_rules! guard_content_type { ($fn_name:ident, $guard_value:literal) => { - #[allow(dead_code)] fn $fn_name(head: &actix_web::dev::RequestHead) -> bool { if let Some(content_type) = head.headers.get("Content-Type") { content_type @@ -33,8 +30,6 @@ macro_rules! guard_content_type { guard_content_type!(guard_json, "application/json"); -type Document = IndexMap; - #[derive(Deserialize)] struct DocumentParam { index_uid: String, @@ -42,21 +37,26 @@ struct DocumentParam { } pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(get_document) - .service(delete_document) - .service(get_all_documents) - .service(add_documents) - .service(update_documents) - .service(delete_documents) - .service(clear_all_documents); + cfg.service( + web::resource("/indexes/{index_uid}/documents") + .route(web::get().to(get_all_documents)) + .route(web::post().guard(guard_json).to(add_documents)) + .route(web::put().guard(guard_json).to(update_documents)) + .route(web::delete().to(clear_all_documents)), + ) + .service( + web::scope("/indexes/{index_uid}/documents/") + .service( + web::resource("{document_id}") + .route(web::get().to(get_document)) + .route(web::delete().to(delete_document)), + ) + .route("/delete-batch", web::post().to(delete_documents)), + ); } -#[get( - "/indexes/{index_uid}/documents/{document_id}", - wrap = "Authentication::Public" -)] async fn get_document( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { let index = path.index_uid.clone(); @@ -68,12 +68,8 @@ async fn get_document( Ok(HttpResponse::Ok().json(document)) } -#[delete( - "/indexes/{index_uid}/documents/{document_id}", - wrap = "Authentication::Private" -)] async fn delete_document( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { let update_status = data @@ -91,9 +87,8 @@ struct BrowseQuery { attributes_to_retrieve: Option, } -#[get("/indexes/{index_uid}/documents", wrap = "Authentication::Public")] async fn get_all_documents( - data: web::Data, + data: GuardedData, path: web::Path, params: web::Query, ) -> Result { @@ -129,9 +124,8 @@ struct UpdateDocumentsQuery { /// Route used when the payload type is "application/json" /// Used to add or replace documents -#[post("/indexes/{index_uid}/documents", wrap = "Authentication::Private")] async fn add_documents( - data: web::Data, + data: GuardedData, path: web::Path, params: web::Query, body: Payload, @@ -151,33 +145,8 @@ async fn add_documents( Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) } -/// Default route for adding documents, this should return an error and redirect to the documentation -#[post("/indexes/{index_uid}/documents", wrap = "Authentication::Private")] -async fn add_documents_default( - _data: web::Data, - _path: web::Path, - _params: web::Query, - _body: web::Json>, -) -> Result { - error!("Unknown document type"); - todo!() -} - -/// Default route for adding documents, this should return an error and redirect to the documentation -#[put("/indexes/{index_uid}/documents", wrap = "Authentication::Private")] -async fn update_documents_default( - _data: web::Data, - _path: web::Path, - _params: web::Query, - _body: web::Json>, -) -> Result { - error!("Unknown document type"); - todo!() -} - -#[put("/indexes/{index_uid}/documents", wrap = "Authentication::Private")] async fn update_documents( - data: web::Data, + data: GuardedData, path: web::Path, params: web::Query, body: Payload, @@ -197,12 +166,8 @@ async fn update_documents( Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update.id() }))) } -#[post( - "/indexes/{index_uid}/documents/delete-batch", - wrap = "Authentication::Private" -)] async fn delete_documents( - data: web::Data, + data: GuardedData, path: web::Path, body: web::Json>, ) -> Result { @@ -221,10 +186,8 @@ async fn delete_documents( Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) } -/// delete all documents -#[delete("/indexes/{index_uid}/documents", wrap = "Authentication::Private")] async fn clear_all_documents( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { let update_status = data.clear_documents(path.index_uid.clone()).await?; diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 160660daf..7f1e265f6 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -9,14 +9,11 @@ use crate::error::ResponseError; use crate::index::{default_crop_length, SearchQuery, DEFAULT_SEARCH_LIMIT}; use crate::routes::IndexParam; use crate::Data; -use crate::extractors::authentication::{Policies, AuthConfig, Public, GuardedData}; +use crate::extractors::authentication::{GuardedData, policies::*}; pub fn services(cfg: &mut web::ServiceConfig) { - let mut policies = Policies::new(); - policies.insert(Public::new()); cfg.service( web::resource("/indexes/{index_uid}/search") - .app_data(AuthConfig::Auth(policies)) .route(web::get().to(search_with_url_query)) .route(web::post().to(search_with_post)), ); @@ -81,7 +78,7 @@ impl From for SearchQuery { } async fn search_with_url_query( - data: GuardedData, + data: GuardedData, path: web::Path, params: web::Query, ) -> Result { From ce4fb8ce20a9a4fbcdc76ab48a32b798c31ef458 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:06:58 +0200 Subject: [PATCH 06/18] update dump route --- meilisearch-http/src/routes/dump.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/meilisearch-http/src/routes/dump.rs b/meilisearch-http/src/routes/dump.rs index f905207ec..f2f276332 100644 --- a/meilisearch-http/src/routes/dump.rs +++ b/meilisearch-http/src/routes/dump.rs @@ -1,18 +1,17 @@ -use actix_web::HttpResponse; -use actix_web::{get, post, web}; use log::debug; +use actix_web::{web, HttpResponse}; use serde::{Deserialize, Serialize}; use crate::error::ResponseError; -use crate::helpers::Authentication; +use crate::extractors::authentication::{policies::*, GuardedData}; use crate::Data; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(create_dump).service(get_dump_status); + cfg.route("/dumps", web::post().to(create_dump)) + .route("/dumps/{dump_uid}/status", web::get().to(get_dump_status)); } -#[post("/dumps", wrap = "Authentication::Private")] -async fn create_dump(data: web::Data) -> Result { +async fn create_dump(data: GuardedData) -> Result { let res = data.create_dump().await?; debug!("returns: {:?}", res); @@ -30,9 +29,8 @@ struct DumpParam { dump_uid: String, } -#[get("/dumps/{dump_uid}/status", wrap = "Authentication::Private")] async fn get_dump_status( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { let res = data.dump_status(path.dump_uid.clone()).await?; From b044608b25b9d35cd1ca1f1b4be98168cb84bc2b Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:08:45 +0200 Subject: [PATCH 07/18] update health route --- meilisearch-http/src/routes/health.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/meilisearch-http/src/routes/health.rs b/meilisearch-http/src/routes/health.rs index 8994df722..172298861 100644 --- a/meilisearch-http/src/routes/health.rs +++ b/meilisearch-http/src/routes/health.rs @@ -1,13 +1,11 @@ -use actix_web::get; use actix_web::{web, HttpResponse}; use crate::error::ResponseError; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(get_health); + cfg.route("/healts", web::get().to(get_health)); } -#[get("/health")] async fn get_health() -> Result { Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" }))) } From fab50256bc97b571b8b5fbf8620a62cd72100693 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:33:21 +0200 Subject: [PATCH 08/18] update index routes --- meilisearch-http/src/routes/index.rs | 97 ++++++++++++++-------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/meilisearch-http/src/routes/index.rs b/meilisearch-http/src/routes/index.rs index 0f29c985e..76a6d5da8 100644 --- a/meilisearch-http/src/routes/index.rs +++ b/meilisearch-http/src/routes/index.rs @@ -1,4 +1,3 @@ -use actix_web::{delete, get, post, put}; use actix_web::{web, HttpResponse}; use chrono::{DateTime, Utc}; use log::debug; @@ -6,34 +5,29 @@ use serde::{Deserialize, Serialize}; use super::{IndexParam, UpdateStatusResponse}; use crate::error::ResponseError; -use crate::helpers::Authentication; +use crate::extractors::authentication::{policies::*, GuardedData}; use crate::Data; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(list_indexes) - .service(get_index) - .service(create_index) - .service(update_index) - .service(delete_index) - .service(get_update_status) - .service(get_all_updates_status); -} - -#[get("/indexes", wrap = "Authentication::Private")] -async fn list_indexes(data: web::Data) -> Result { - let indexes = data.list_indexes().await?; - debug!("returns: {:?}", indexes); - Ok(HttpResponse::Ok().json(indexes)) -} - -#[get("/indexes/{index_uid}", wrap = "Authentication::Private")] -async fn get_index( - data: web::Data, - path: web::Path, -) -> Result { - let meta = data.index(path.index_uid.clone()).await?; - debug!("returns: {:?}", meta); - Ok(HttpResponse::Ok().json(meta)) + cfg.service( + web::resource("indexes") + .route(web::get().to(list_indexes)) + .route(web::post().to(create_index)), + ) + .service( + web::resource("/indexes/{index_uid}") + .route(web::get().to(get_index)) + .route(web::put().to(update_index)) + .route(web::delete().to(delete_index)), + ) + .route( + "/indexes/{index_uid}/updates", + web::get().to(get_all_updates_status), + ) + .route( + "/indexes/{index_uid}/updates/{update_id}", + web::get().to(get_update_status), + ); } #[derive(Debug, Deserialize)] @@ -43,18 +37,6 @@ struct IndexCreateRequest { primary_key: Option, } -#[post("/indexes", wrap = "Authentication::Private")] -async fn create_index( - data: web::Data, - body: web::Json, -) -> Result { - debug!("called with params: {:?}", body); - let body = body.into_inner(); - let meta = data.create_index(body.uid, body.primary_key).await?; - debug!("returns: {:?}", meta); - Ok(HttpResponse::Ok().json(meta)) -} - #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] struct UpdateIndexRequest { @@ -72,9 +54,32 @@ pub struct UpdateIndexResponse { primary_key: Option, } -#[put("/indexes/{index_uid}", wrap = "Authentication::Private")] +async fn list_indexes(data: GuardedData) -> Result { + let indexes = data.list_indexes().await?; + debug!("returns: {:?}", indexes); + Ok(HttpResponse::Ok().json(indexes)) +} + +async fn create_index( + data: GuardedData, + body: web::Json, +) -> Result { + let body = body.into_inner(); + let meta = data.create_index(body.uid, body.primary_key).await?; + Ok(HttpResponse::Ok().json(meta)) +} + +async fn get_index( + data: GuardedData, + path: web::Path, +) -> Result { + let meta = data.index(path.index_uid.clone()).await?; + debug!("returns: {:?}", meta); + Ok(HttpResponse::Ok().json(meta)) +} + async fn update_index( - data: web::Data, + data: GuardedData, path: web::Path, body: web::Json, ) -> Result { @@ -87,9 +92,8 @@ async fn update_index( Ok(HttpResponse::Ok().json(meta)) } -#[delete("/indexes/{index_uid}", wrap = "Authentication::Private")] async fn delete_index( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { data.delete_index(path.index_uid.clone()).await?; @@ -102,12 +106,8 @@ struct UpdateParam { update_id: u64, } -#[get( - "/indexes/{index_uid}/updates/{update_id}", - wrap = "Authentication::Private" -)] async fn get_update_status( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { let params = path.into_inner(); @@ -119,9 +119,8 @@ async fn get_update_status( Ok(HttpResponse::Ok().json(meta)) } -#[get("/indexes/{index_uid}/updates", wrap = "Authentication::Private")] async fn get_all_updates_status( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { let metas = data.get_updates_status(path.into_inner().index_uid).await?; From 817fcfdd8881ac503d3a10bf318f93acc5524db2 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:37:18 +0200 Subject: [PATCH 09/18] update keys route --- meilisearch-http/src/routes/key.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/meilisearch-http/src/routes/key.rs b/meilisearch-http/src/routes/key.rs index b44d747c8..51b1c78a2 100644 --- a/meilisearch-http/src/routes/key.rs +++ b/meilisearch-http/src/routes/key.rs @@ -1,13 +1,11 @@ -use actix_web::get; -use actix_web::web; -use actix_web::HttpResponse; +use actix_web::{web, HttpResponse}; use serde::Serialize; -use crate::helpers::Authentication; use crate::Data; +use crate::extractors::authentication::{GuardedData, policies::*}; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(list); + cfg.route("/keys", web::get().to(list)); } #[derive(Serialize)] @@ -16,8 +14,7 @@ struct KeysResponse { public: Option, } -#[get("/keys", wrap = "Authentication::Admin")] -async fn list(data: web::Data) -> HttpResponse { +async fn list(data: GuardedData) -> HttpResponse { let api_keys = data.api_keys.clone(); HttpResponse::Ok().json(&KeysResponse { private: api_keys.private, From 1e9f374ff8d2b25f6c6904ddb937f6be91c5499d Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:40:04 +0200 Subject: [PATCH 10/18] update running route --- meilisearch-http/src/lib.rs | 4 ++-- meilisearch-http/src/routes/mod.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/meilisearch-http/src/lib.rs b/meilisearch-http/src/lib.rs index 7b9dbdc21..78d9ec8b1 100644 --- a/meilisearch-http/src/lib.rs +++ b/meilisearch-http/src/lib.rs @@ -88,13 +88,13 @@ pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) { } config.service(scope); } else { - config.service(routes::running); + config.route("/", web::get().to(routes::running)); } } #[cfg(not(feature = "mini-dashboard"))] pub fn dashboard(config: &mut web::ServiceConfig, _enable_frontend: bool) { - config.service(routes::running); + config.route("/", web::get().to(routes::running)); } #[macro_export] diff --git a/meilisearch-http/src/routes/mod.rs b/meilisearch-http/src/routes/mod.rs index f4581ebcb..520949cd8 100644 --- a/meilisearch-http/src/routes/mod.rs +++ b/meilisearch-http/src/routes/mod.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use actix_web::{get, HttpResponse}; +use actix_web::HttpResponse; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -220,7 +220,6 @@ impl IndexUpdateResponse { /// "status": "Meilisearch is running" /// } /// ``` -#[get("/")] pub async fn running() -> HttpResponse { HttpResponse::Ok().json(serde_json::json!({ "status": "MeiliSearch is running" })) } From 549b489c8ad300836aa2e40792f9522b4631d104 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:50:46 +0200 Subject: [PATCH 11/18] update settings routes --- meilisearch-http/src/routes/settings.rs | 52 ++++++++++++------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/meilisearch-http/src/routes/settings.rs b/meilisearch-http/src/routes/settings.rs index 5e4ecf1a4..f7df7b701 100644 --- a/meilisearch-http/src/routes/settings.rs +++ b/meilisearch-http/src/routes/settings.rs @@ -1,7 +1,7 @@ -use actix_web::{delete, get, post, web, HttpResponse}; use log::debug; +use actix_web::{web, HttpResponse}; -use crate::helpers::Authentication; +use crate::extractors::authentication::{GuardedData, policies::*}; use crate::index::Settings; use crate::Data; use crate::{error::ResponseError, index::Unchecked}; @@ -11,16 +11,15 @@ macro_rules! make_setting_route { ($route:literal, $type:ty, $attr:ident, $camelcase_attr:literal) => { mod $attr { use log::debug; - use actix_web::{web, HttpResponse}; + use actix_web::{web, HttpResponse, Resource}; use crate::data; use crate::error::ResponseError; - use crate::helpers::Authentication; use crate::index::Settings; + use crate::extractors::authentication::{GuardedData, policies::*}; - #[actix_web::delete($route, wrap = "Authentication::Private")] - pub async fn delete( - data: web::Data, + async fn delete( + data: GuardedData, index_uid: web::Path, ) -> Result { use crate::index::Settings; @@ -33,9 +32,8 @@ macro_rules! make_setting_route { Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) } - #[actix_web::post($route, wrap = "Authentication::Private")] - pub async fn update( - data: actix_web::web::Data, + async fn update( + data: GuardedData, index_uid: actix_web::web::Path, body: actix_web::web::Json>, ) -> std::result::Result { @@ -49,9 +47,8 @@ macro_rules! make_setting_route { Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() }))) } - #[actix_web::get($route, wrap = "Authentication::Private")] - pub async fn get( - data: actix_web::web::Data, + async fn get( + data: GuardedData, index_uid: actix_web::web::Path, ) -> std::result::Result { let settings = data.settings(index_uid.into_inner()).await?; @@ -60,6 +57,13 @@ macro_rules! make_setting_route { let val = json[$camelcase_attr].take(); Ok(HttpResponse::Ok().json(val)) } + + pub fn resources() -> Resource { + Resource::new($route) + .route(web::get().to(get)) + .route(web::post().to(update)) + .route(web::delete().to(delete)) + } } }; } @@ -117,14 +121,11 @@ macro_rules! create_services { ($($mod:ident),*) => { pub fn services(cfg: &mut web::ServiceConfig) { cfg - .service(update_all) - .service(get_all) - .service(delete_all) - $( - .service($mod::get) - .service($mod::update) - .service($mod::delete) - )*; + .service(web::resource("/indexes/{index_uid}/settings") + .route(web::post().to(update_all)) + .route(web::get().to(get_all)) + .route(web::delete().to(delete_all))) + $(.service($mod::resources()))*; } }; } @@ -139,9 +140,8 @@ create_services!( ranking_rules ); -#[post("/indexes/{index_uid}/settings", wrap = "Authentication::Private")] async fn update_all( - data: web::Data, + data: GuardedData, index_uid: web::Path, body: web::Json>, ) -> Result { @@ -154,9 +154,8 @@ async fn update_all( Ok(HttpResponse::Accepted().json(json)) } -#[get("/indexes/{index_uid}/settings", wrap = "Authentication::Private")] async fn get_all( - data: web::Data, + data: GuardedData, index_uid: web::Path, ) -> Result { let settings = data.settings(index_uid.into_inner()).await?; @@ -164,9 +163,8 @@ async fn get_all( Ok(HttpResponse::Ok().json(settings)) } -#[delete("/indexes/{index_uid}/settings", wrap = "Authentication::Private")] async fn delete_all( - data: web::Data, + data: GuardedData, index_uid: web::Path, ) -> Result { let settings = Settings::cleared(); From 561596d8bc7ec8d9bb08cee863cfa8c29c45503c Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:54:51 +0200 Subject: [PATCH 12/18] update stats routes --- meilisearch-http/src/routes/stats.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/meilisearch-http/src/routes/stats.rs b/meilisearch-http/src/routes/stats.rs index c39989188..bfebba6ed 100644 --- a/meilisearch-http/src/routes/stats.rs +++ b/meilisearch-http/src/routes/stats.rs @@ -1,23 +1,20 @@ -use actix_web::get; -use actix_web::web; -use actix_web::HttpResponse; use log::debug; +use actix_web::{web, HttpResponse}; use serde::Serialize; use crate::error::ResponseError; -use crate::helpers::Authentication; +use crate::extractors::authentication::{GuardedData, policies::*}; use crate::routes::IndexParam; use crate::Data; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(get_index_stats) - .service(get_stats) - .service(get_version); + cfg.route("/indexes/{index_uid}/stats", web::get().to(get_index_stats)) + .route("/stats", web::get().to(get_stats)) + .route("/version", web::get().to(get_version)); } -#[get("/indexes/{index_uid}/stats", wrap = "Authentication::Private")] async fn get_index_stats( - data: web::Data, + data: GuardedData, path: web::Path, ) -> Result { let response = data.get_index_stats(path.index_uid.clone()).await?; @@ -26,8 +23,7 @@ async fn get_index_stats( Ok(HttpResponse::Ok().json(response)) } -#[get("/stats", wrap = "Authentication::Private")] -async fn get_stats(data: web::Data) -> Result { +async fn get_stats(data: GuardedData) -> Result { let response = data.get_all_stats().await?; debug!("returns: {:?}", response); @@ -42,8 +38,7 @@ struct VersionResponse { pkg_version: String, } -#[get("/version", wrap = "Authentication::Private")] -async fn get_version() -> HttpResponse { +async fn get_version(_data: GuardedData) -> HttpResponse { let commit_sha = match option_env!("COMMIT_SHA") { Some("") | None => env!("VERGEN_SHA"), Some(commit_sha) => commit_sha, From d078cbf39b3036a397512e17bb832a6c75ac0460 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 15:56:12 +0200 Subject: [PATCH 13/18] remove authentication middleware --- .../src/helpers/authentication.rs | 150 ------------------ meilisearch-http/src/helpers/mod.rs | 2 - 2 files changed, 152 deletions(-) delete mode 100644 meilisearch-http/src/helpers/authentication.rs diff --git a/meilisearch-http/src/helpers/authentication.rs b/meilisearch-http/src/helpers/authentication.rs deleted file mode 100644 index dddf57138..000000000 --- a/meilisearch-http/src/helpers/authentication.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_web::body::Body; -use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; -use actix_web::web; -use actix_web::ResponseError as _; -use futures::future::{ok, Future, Ready}; -use futures::ready; -use pin_project::pin_project; - -use crate::error::{AuthenticationError, ResponseError}; -use crate::Data; - -#[derive(Clone, Copy)] -pub enum Authentication { - Public, - Private, - Admin, -} - -impl Transform for Authentication -where - S: Service, Error = actix_web::Error>, -{ - type Response = ServiceResponse; - type Error = actix_web::Error; - type InitError = (); - type Transform = LoggingMiddleware; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(LoggingMiddleware { - acl: *self, - service, - }) - } -} - -pub struct LoggingMiddleware { - acl: Authentication, - service: S, -} - -#[allow(clippy::type_complexity)] -impl Service for LoggingMiddleware -where - S: Service, Error = actix_web::Error>, -{ - type Response = ServiceResponse; - type Error = actix_web::Error; - type Future = AuthenticationFuture; - - fn poll_ready(&self, cx: &mut Context) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&self, req: ServiceRequest) -> Self::Future { - let data = req.app_data::>().unwrap(); - - if data.api_keys().master.is_none() { - return AuthenticationFuture::Authenticated(self.service.call(req)); - } - - let auth_header = match req.headers().get("X-Meili-API-Key") { - Some(auth) => match auth.to_str() { - Ok(auth) => auth, - Err(_) => return AuthenticationFuture::NoHeader(Some(req)), - }, - None => return AuthenticationFuture::NoHeader(Some(req)), - }; - - let authenticated = match self.acl { - Authentication::Admin => data.api_keys().master.as_deref() == Some(auth_header), - Authentication::Private => { - data.api_keys().master.as_deref() == Some(auth_header) - || data.api_keys().private.as_deref() == Some(auth_header) - } - Authentication::Public => { - data.api_keys().master.as_deref() == Some(auth_header) - || data.api_keys().private.as_deref() == Some(auth_header) - || data.api_keys().public.as_deref() == Some(auth_header) - } - }; - - if authenticated { - AuthenticationFuture::Authenticated(self.service.call(req)) - } else { - AuthenticationFuture::Refused(Some(req)) - } - } -} - -#[pin_project(project = AuthProj)] -pub enum AuthenticationFuture -where - S: Service, -{ - Authenticated(#[pin] S::Future), - NoHeader(Option), - Refused(Option), -} - -impl Future for AuthenticationFuture -where - S: Service, Error = actix_web::Error>, -{ - type Output = Result, actix_web::Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - match this { - AuthProj::Authenticated(fut) => match ready!(fut.poll(cx)) { - Ok(resp) => Poll::Ready(Ok(resp)), - Err(e) => Poll::Ready(Err(e)), - }, - AuthProj::NoHeader(req) => { - match req.take() { - Some(req) => { - let response = - ResponseError::from(AuthenticationError::MissingAuthorizationHeader); - let response = response.error_response(); - let response = req.into_response(response); - Poll::Ready(Ok(response)) - } - // https://doc.rust-lang.org/nightly/std/future/trait.Future.html#panics - None => unreachable!("poll called again on ready future"), - } - } - AuthProj::Refused(req) => { - match req.take() { - Some(req) => { - let bad_token = req - .headers() - .get("X-Meili-API-Key") - .map(|h| h.to_str().map(String::from).unwrap_or_default()) - .unwrap_or_default(); - let response = - ResponseError::from(AuthenticationError::InvalidToken(bad_token)); - let response = response.error_response(); - let response = req.into_response(response); - Poll::Ready(Ok(response)) - } - // https://doc.rust-lang.org/nightly/std/future/trait.Future.html#panics - None => unreachable!("poll called again on ready future"), - } - } - } - } -} diff --git a/meilisearch-http/src/helpers/mod.rs b/meilisearch-http/src/helpers/mod.rs index a5cddf29c..c664f15aa 100644 --- a/meilisearch-http/src/helpers/mod.rs +++ b/meilisearch-http/src/helpers/mod.rs @@ -1,6 +1,4 @@ -pub mod authentication; pub mod compression; mod env; -pub use authentication::Authentication; pub use env::EnvSizer; From 8e4928c7eaf25f7e174252ccf1acf99077bb5684 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 16:25:27 +0200 Subject: [PATCH 14/18] fix tests --- meilisearch-http/src/routes/document.rs | 15 ++++++++------- meilisearch-http/src/routes/health.rs | 2 +- .../tests/documents/delete_documents.rs | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/meilisearch-http/src/routes/document.rs b/meilisearch-http/src/routes/document.rs index 4824ec7b8..abea85ed6 100644 --- a/meilisearch-http/src/routes/document.rs +++ b/meilisearch-http/src/routes/document.rs @@ -44,14 +44,15 @@ pub fn services(cfg: &mut web::ServiceConfig) { .route(web::put().guard(guard_json).to(update_documents)) .route(web::delete().to(clear_all_documents)), ) + // this route needs to be before the /documents/{document_id} to match properly + .route( + "/indexes/{index_uid}/documents/delete-batch", + web::post().to(delete_documents), + ) .service( - web::scope("/indexes/{index_uid}/documents/") - .service( - web::resource("{document_id}") - .route(web::get().to(get_document)) - .route(web::delete().to(delete_document)), - ) - .route("/delete-batch", web::post().to(delete_documents)), + web::resource("/indexes/{index_uid}/documents/{document_id}") + .route(web::get().to(get_document)) + .route(web::delete().to(delete_document)), ); } diff --git a/meilisearch-http/src/routes/health.rs b/meilisearch-http/src/routes/health.rs index 172298861..3c7200200 100644 --- a/meilisearch-http/src/routes/health.rs +++ b/meilisearch-http/src/routes/health.rs @@ -3,7 +3,7 @@ use actix_web::{web, HttpResponse}; use crate::error::ResponseError; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.route("/healts", web::get().to(get_health)); + cfg.route("/health", web::get().to(get_health)); } async fn get_health() -> Result { diff --git a/meilisearch-http/tests/documents/delete_documents.rs b/meilisearch-http/tests/documents/delete_documents.rs index d5794e40c..eb6fa040b 100644 --- a/meilisearch-http/tests/documents/delete_documents.rs +++ b/meilisearch-http/tests/documents/delete_documents.rs @@ -14,8 +14,8 @@ async fn delete_one_unexisting_document() { let server = Server::new().await; let index = server.index("test"); index.create(None).await; - let (_response, code) = index.delete_document(0).await; - assert_eq!(code, 202); + let (response, code) = index.delete_document(0).await; + assert_eq!(code, 202, "{}", response); let update = index.wait_update_id(0).await; assert_eq!(update["status"], "processed"); } @@ -85,8 +85,8 @@ async fn clear_all_documents_empty_index() { #[actix_rt::test] async fn delete_batch_unexisting_index() { let server = Server::new().await; - let (_response, code) = server.index("test").delete_batch(vec![]).await; - assert_eq!(code, 404); + let (response, code) = server.index("test").delete_batch(vec![]).await; + assert_eq!(code, 404, "{}", response); } #[actix_rt::test] From 79fc3bb84e3659797fc3ca962854fc026e7b6684 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 16:25:52 +0200 Subject: [PATCH 15/18] fmt --- meilisearch-http/src/lib.rs | 40 ++++++++++--------- meilisearch-http/src/routes/key.rs | 2 +- meilisearch-http/src/routes/search.rs | 2 +- meilisearch-http/src/routes/settings.rs | 2 +- meilisearch-http/src/routes/stats.rs | 2 +- .../tests/settings/get_settings.rs | 7 +++- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/meilisearch-http/src/lib.rs b/meilisearch-http/src/lib.rs index 78d9ec8b1..ffdf62eb7 100644 --- a/meilisearch-http/src/lib.rs +++ b/meilisearch-http/src/lib.rs @@ -19,8 +19,8 @@ pub use option::Opt; use actix_web::web; -use extractors::payload::PayloadConfig; use extractors::authentication::policies::*; +use extractors::payload::PayloadConfig; pub fn configure_data(config: &mut web::ServiceConfig, data: Data) { let http_payload_size_limit = data.http_payload_size_limit(); @@ -42,7 +42,7 @@ pub fn configure_data(config: &mut web::ServiceConfig, data: Data) { pub fn configure_auth(config: &mut web::ServiceConfig, data: &Data) { let keys = data.api_keys(); - let auth_config = if let Some(ref master_key) = keys.master { + let auth_config = if let Some(ref master_key) = keys.master { let private_key = keys.private.as_ref().unwrap(); let public_key = keys.public.as_ref().unwrap(); let mut policies = init_policies!(Public, Private, Admin); @@ -62,8 +62,8 @@ pub fn configure_auth(config: &mut web::ServiceConfig, data: &Data) { #[cfg(feature = "mini-dashboard")] pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) { - use actix_web_static_files::Resource; use actix_web::HttpResponse; + use actix_web_static_files::Resource; mod generated { include!(concat!(env!("OUT_DIR"), "/generated.rs")); @@ -71,22 +71,24 @@ pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) { if enable_frontend { let generated = generated::generate(); - let mut scope = web::scope("/"); - // Generate routes for mini-dashboard assets - for (path, resource) in generated.into_iter() { - let Resource {mime_type, data, ..} = resource; - // Redirect index.html to / - if path == "index.html" { - config.service(web::resource("/").route(web::get().to(move || { - HttpResponse::Ok().content_type(mime_type).body(data) - }))); - } else { - scope = scope.service(web::resource(path).route(web::get().to(move || { - HttpResponse::Ok().content_type(mime_type).body(data) - }))); - } + let mut scope = web::scope("/"); + // Generate routes for mini-dashboard assets + for (path, resource) in generated.into_iter() { + let Resource { + mime_type, data, .. + } = resource; + // Redirect index.html to / + if path == "index.html" { + config.service(web::resource("/").route( + web::get().to(move || HttpResponse::Ok().content_type(mime_type).body(data)), + )); + } else { + scope = scope.service(web::resource(path).route( + web::get().to(move || HttpResponse::Ok().content_type(mime_type).body(data)), + )); } - config.service(scope); + } + config.service(scope); } else { config.route("/", web::get().to(routes::running)); } @@ -105,7 +107,7 @@ macro_rules! create_app { use actix_web::App; use actix_web::{middleware, web}; use meilisearch_http::routes::*; - use meilisearch_http::{configure_data, dashboard, configure_auth}; + use meilisearch_http::{configure_auth, configure_data, dashboard}; App::new() .configure(|s| configure_data(s, $data.clone())) diff --git a/meilisearch-http/src/routes/key.rs b/meilisearch-http/src/routes/key.rs index 51b1c78a2..1ea400a4d 100644 --- a/meilisearch-http/src/routes/key.rs +++ b/meilisearch-http/src/routes/key.rs @@ -1,8 +1,8 @@ use actix_web::{web, HttpResponse}; use serde::Serialize; +use crate::extractors::authentication::{policies::*, GuardedData}; use crate::Data; -use crate::extractors::authentication::{GuardedData, policies::*}; pub fn services(cfg: &mut web::ServiceConfig) { cfg.route("/keys", web::get().to(list)); diff --git a/meilisearch-http/src/routes/search.rs b/meilisearch-http/src/routes/search.rs index 7f1e265f6..7307a5990 100644 --- a/meilisearch-http/src/routes/search.rs +++ b/meilisearch-http/src/routes/search.rs @@ -6,10 +6,10 @@ use serde::Deserialize; use serde_json::Value; use crate::error::ResponseError; +use crate::extractors::authentication::{policies::*, GuardedData}; use crate::index::{default_crop_length, SearchQuery, DEFAULT_SEARCH_LIMIT}; use crate::routes::IndexParam; use crate::Data; -use crate::extractors::authentication::{GuardedData, policies::*}; pub fn services(cfg: &mut web::ServiceConfig) { cfg.service( diff --git a/meilisearch-http/src/routes/settings.rs b/meilisearch-http/src/routes/settings.rs index f7df7b701..6a8d9cca5 100644 --- a/meilisearch-http/src/routes/settings.rs +++ b/meilisearch-http/src/routes/settings.rs @@ -1,7 +1,7 @@ use log::debug; use actix_web::{web, HttpResponse}; -use crate::extractors::authentication::{GuardedData, policies::*}; +use crate::extractors::authentication::{policies::*, GuardedData}; use crate::index::Settings; use crate::Data; use crate::{error::ResponseError, index::Unchecked}; diff --git a/meilisearch-http/src/routes/stats.rs b/meilisearch-http/src/routes/stats.rs index bfebba6ed..33bb482d9 100644 --- a/meilisearch-http/src/routes/stats.rs +++ b/meilisearch-http/src/routes/stats.rs @@ -3,7 +3,7 @@ use actix_web::{web, HttpResponse}; use serde::Serialize; use crate::error::ResponseError; -use crate::extractors::authentication::{GuardedData, policies::*}; +use crate::extractors::authentication::{policies::*, GuardedData}; use crate::routes::IndexParam; use crate::Data; diff --git a/meilisearch-http/tests/settings/get_settings.rs b/meilisearch-http/tests/settings/get_settings.rs index 4941b5d2f..0b523eef3 100644 --- a/meilisearch-http/tests/settings/get_settings.rs +++ b/meilisearch-http/tests/settings/get_settings.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use once_cell::sync::Lazy; -use serde_json::{Value, json}; +use serde_json::{json, Value}; use crate::common::Server; @@ -11,7 +11,10 @@ static DEFAULT_SETTINGS_VALUES: Lazy> = Lazy::new(| map.insert("searchable_attributes", json!(["*"])); map.insert("filterable_attributes", json!([])); map.insert("distinct_attribute", json!(Value::Null)); - map.insert("ranking_rules", json!(["words", "typo", "proximity", "attribute", "exactness"])); + map.insert( + "ranking_rules", + json!(["words", "typo", "proximity", "attribute", "exactness"]), + ); map.insert("stop_words", json!([])); map.insert("synonyms", json!({})); map From fbd58f2eec0e93e7052633c9105a940cd8f5a61e Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 16:27:13 +0200 Subject: [PATCH 16/18] clippy --- meilisearch-http/src/extractors/authentication/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meilisearch-http/src/extractors/authentication/mod.rs b/meilisearch-http/src/extractors/authentication/mod.rs index 2cce6911d..fd3272e2f 100644 --- a/meilisearch-http/src/extractors/authentication/mod.rs +++ b/meilisearch-http/src/extractors/authentication/mod.rs @@ -16,7 +16,7 @@ macro_rules! create_policies { use crate::extractors::authentication::Policy; $( - #[derive(Debug)] + #[derive(Debug, Default)] pub struct $name { inner: HashSet> } From 3b601f615a455a8c089c86dc06cc92f5de3f4782 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 16:53:20 +0200 Subject: [PATCH 17/18] declare new authentication related errors --- meilisearch-http/src/error.rs | 17 ------------ .../src/extractors/authentication/error.rs | 26 +++++++++++++++++++ .../src/extractors/authentication/mod.rs | 13 ++++++---- 3 files changed, 34 insertions(+), 22 deletions(-) create mode 100644 meilisearch-http/src/extractors/authentication/error.rs diff --git a/meilisearch-http/src/error.rs b/meilisearch-http/src/error.rs index 6124ed880..4f47abd66 100644 --- a/meilisearch-http/src/error.rs +++ b/meilisearch-http/src/error.rs @@ -10,23 +10,6 @@ use meilisearch_error::{Code, ErrorCode}; use milli::UserError; use serde::{Deserialize, Serialize}; -#[derive(Debug, thiserror::Error)] -pub enum AuthenticationError { - #[error("You must have an authorization token")] - MissingAuthorizationHeader, - #[error("Invalid API key")] - InvalidToken(String), -} - -impl ErrorCode for AuthenticationError { - fn error_code(&self) -> Code { - match self { - AuthenticationError::MissingAuthorizationHeader => Code::MissingAuthorizationHeader, - AuthenticationError::InvalidToken(_) => Code::InvalidToken, - } - } -} - #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ResponseError { diff --git a/meilisearch-http/src/extractors/authentication/error.rs b/meilisearch-http/src/extractors/authentication/error.rs new file mode 100644 index 000000000..29578e373 --- /dev/null +++ b/meilisearch-http/src/extractors/authentication/error.rs @@ -0,0 +1,26 @@ +use meilisearch_error::{Code, ErrorCode}; + +#[derive(Debug, thiserror::Error)] +pub enum AuthenticationError { + #[error("You must have an authorization token")] + MissingAuthorizationHeader, + #[error("Invalid API key")] + InvalidToken(String), + // Triggered on configuration error. + #[error("Irretrievable state")] + IrretrievableState, + #[error("Unknown authentication policy")] + UnknownPolicy, +} + +impl ErrorCode for AuthenticationError { + fn error_code(&self) -> Code { + match self { + AuthenticationError::MissingAuthorizationHeader => Code::MissingAuthorizationHeader, + AuthenticationError::InvalidToken(_) => Code::InvalidToken, + AuthenticationError::IrretrievableState => Code::Internal, + AuthenticationError::UnknownPolicy => Code::Internal, + } + } +} + diff --git a/meilisearch-http/src/extractors/authentication/mod.rs b/meilisearch-http/src/extractors/authentication/mod.rs index fd3272e2f..6b9ac24ae 100644 --- a/meilisearch-http/src/extractors/authentication/mod.rs +++ b/meilisearch-http/src/extractors/authentication/mod.rs @@ -1,3 +1,5 @@ +mod error; + use std::any::{Any, TypeId}; use std::collections::HashMap; use std::marker::PhantomData; @@ -7,7 +9,8 @@ use actix_web::FromRequest; use futures::future::err; use futures::future::{ok, Ready}; -use crate::error::{AuthenticationError, ResponseError}; +use crate::error::ResponseError; +use error::AuthenticationError; macro_rules! create_policies { ($($name:ident), *) => { @@ -151,7 +154,7 @@ impl FromRequest for GuardedData data, _marker: PhantomData, }), - None => todo!("Data not configured"), + None => err(AuthenticationError::IrretrievableState.into()), }, AuthConfig::Auth(policies) => match policies.get::

() { Some(policy) => match req.headers().get("x-meili-api-key") { @@ -162,7 +165,7 @@ impl FromRequest for GuardedData data, _marker: PhantomData, }), - None => todo!("Data not configured"), + None => err(AuthenticationError::IrretrievableState.into()), } } else { err(AuthenticationError::InvalidToken(String::from("hello")).into()) @@ -170,10 +173,10 @@ impl FromRequest for GuardedData } None => err(AuthenticationError::MissingAuthorizationHeader.into()), }, - None => todo!("no policy found"), + None => err(AuthenticationError::UnknownPolicy.into()), }, }, - None => todo!(), + None => err(AuthenticationError::IrretrievableState.into()), } } } From 01b09c065b29da38ce78e25b75bf1becc9e58df8 Mon Sep 17 00:00:00 2001 From: marin postma Date: Thu, 24 Jun 2021 19:02:28 +0200 Subject: [PATCH 18/18] change route to service --- meilisearch-http/src/lib.rs | 4 ++-- meilisearch-http/src/routes/document.rs | 30 ++++++++++++------------- meilisearch-http/src/routes/dump.rs | 4 ++-- meilisearch-http/src/routes/health.rs | 2 +- meilisearch-http/src/routes/index.rs | 12 +++++----- meilisearch-http/src/routes/key.rs | 2 +- meilisearch-http/src/routes/stats.rs | 6 ++--- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/meilisearch-http/src/lib.rs b/meilisearch-http/src/lib.rs index ffdf62eb7..0eb61f84c 100644 --- a/meilisearch-http/src/lib.rs +++ b/meilisearch-http/src/lib.rs @@ -90,13 +90,13 @@ pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) { } config.service(scope); } else { - config.route("/", web::get().to(routes::running)); + config.service(web::resource("/").route(web::get().to(routes::running))); } } #[cfg(not(feature = "mini-dashboard"))] pub fn dashboard(config: &mut web::ServiceConfig, _enable_frontend: bool) { - config.route("/", web::get().to(routes::running)); + config.service(web::resource("/").route(web::get().to(routes::running))); } #[macro_export] diff --git a/meilisearch-http/src/routes/document.rs b/meilisearch-http/src/routes/document.rs index abea85ed6..817f624d0 100644 --- a/meilisearch-http/src/routes/document.rs +++ b/meilisearch-http/src/routes/document.rs @@ -38,21 +38,21 @@ struct DocumentParam { pub fn services(cfg: &mut web::ServiceConfig) { cfg.service( - web::resource("/indexes/{index_uid}/documents") - .route(web::get().to(get_all_documents)) - .route(web::post().guard(guard_json).to(add_documents)) - .route(web::put().guard(guard_json).to(update_documents)) - .route(web::delete().to(clear_all_documents)), - ) - // this route needs to be before the /documents/{document_id} to match properly - .route( - "/indexes/{index_uid}/documents/delete-batch", - web::post().to(delete_documents), - ) - .service( - web::resource("/indexes/{index_uid}/documents/{document_id}") - .route(web::get().to(get_document)) - .route(web::delete().to(delete_document)), + web::scope("/indexes/{index_uid}/documents") + .service( + web::resource("") + .route(web::get().to(get_all_documents)) + .route(web::post().guard(guard_json).to(add_documents)) + .route(web::put().guard(guard_json).to(update_documents)) + .route(web::delete().to(clear_all_documents)), + ) + // this route needs to be before the /documents/{document_id} to match properly + .service(web::resource("/delete-batch").route(web::post().to(delete_documents))) + .service( + web::resource("/{document_id}") + .route(web::get().to(get_document)) + .route(web::delete().to(delete_document)), + ), ); } diff --git a/meilisearch-http/src/routes/dump.rs b/meilisearch-http/src/routes/dump.rs index f2f276332..e506755a1 100644 --- a/meilisearch-http/src/routes/dump.rs +++ b/meilisearch-http/src/routes/dump.rs @@ -7,8 +7,8 @@ use crate::extractors::authentication::{policies::*, GuardedData}; use crate::Data; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.route("/dumps", web::post().to(create_dump)) - .route("/dumps/{dump_uid}/status", web::get().to(get_dump_status)); + cfg.service(web::resource("/dumps").route(web::post().to(create_dump))) + .service(web::resource("/dumps/{dump_uid}/status").route(web::get().to(get_dump_status))); } async fn create_dump(data: GuardedData) -> Result { diff --git a/meilisearch-http/src/routes/health.rs b/meilisearch-http/src/routes/health.rs index 3c7200200..54237de1a 100644 --- a/meilisearch-http/src/routes/health.rs +++ b/meilisearch-http/src/routes/health.rs @@ -3,7 +3,7 @@ use actix_web::{web, HttpResponse}; use crate::error::ResponseError; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.route("/health", web::get().to(get_health)); + cfg.service(web::resource("/health").route(web::get().to(get_health))); } async fn get_health() -> Result { diff --git a/meilisearch-http/src/routes/index.rs b/meilisearch-http/src/routes/index.rs index 76a6d5da8..eb8da92ed 100644 --- a/meilisearch-http/src/routes/index.rs +++ b/meilisearch-http/src/routes/index.rs @@ -20,13 +20,13 @@ pub fn services(cfg: &mut web::ServiceConfig) { .route(web::put().to(update_index)) .route(web::delete().to(delete_index)), ) - .route( - "/indexes/{index_uid}/updates", - web::get().to(get_all_updates_status), + .service( + web::resource("/indexes/{index_uid}/updates") + .route(web::get().to(get_all_updates_status)) ) - .route( - "/indexes/{index_uid}/updates/{update_id}", - web::get().to(get_update_status), + .service( + web::resource("/indexes/{index_uid}/updates/{update_id}") + .route(web::get().to(get_update_status)) ); } diff --git a/meilisearch-http/src/routes/key.rs b/meilisearch-http/src/routes/key.rs index 1ea400a4d..d47e264be 100644 --- a/meilisearch-http/src/routes/key.rs +++ b/meilisearch-http/src/routes/key.rs @@ -5,7 +5,7 @@ use crate::extractors::authentication::{policies::*, GuardedData}; use crate::Data; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.route("/keys", web::get().to(list)); + cfg.service(web::resource("/keys").route(web::get().to(list))); } #[derive(Serialize)] diff --git a/meilisearch-http/src/routes/stats.rs b/meilisearch-http/src/routes/stats.rs index 33bb482d9..e440ce8ff 100644 --- a/meilisearch-http/src/routes/stats.rs +++ b/meilisearch-http/src/routes/stats.rs @@ -8,9 +8,9 @@ use crate::routes::IndexParam; use crate::Data; pub fn services(cfg: &mut web::ServiceConfig) { - cfg.route("/indexes/{index_uid}/stats", web::get().to(get_index_stats)) - .route("/stats", web::get().to(get_stats)) - .route("/version", web::get().to(get_version)); + cfg.service(web::resource("/indexes/{index_uid}/stats").route(web::get().to(get_index_stats))) + .service(web::resource("/stats").route(web::get().to(get_stats))) + .service(web::resource("/version").route(web::get().to(get_version))); } async fn get_index_stats(