From 5e2861ff55428d447c7f95155cd6e5c20ae554c1 Mon Sep 17 00:00:00 2001 From: Quentin de Quelen Date: Wed, 15 Apr 2020 10:51:15 +0200 Subject: [PATCH] prepare architecture for tests --- meilisearch-http/src/data.rs | 2 +- .../src/helpers/authentication.rs | 100 +++++++++++++ meilisearch-http/src/helpers/mod.rs | 4 +- meilisearch-http/src/helpers/tide.rs | 83 ----------- meilisearch-http/src/lib.rs | 106 ++++++++++++++ meilisearch-http/src/main.rs | 60 +------- meilisearch-http/src/routes/mod.rs | 135 ------------------ 7 files changed, 213 insertions(+), 277 deletions(-) create mode 100644 meilisearch-http/src/helpers/authentication.rs delete mode 100644 meilisearch-http/src/helpers/tide.rs diff --git a/meilisearch-http/src/data.rs b/meilisearch-http/src/data.rs index 23f7efb69..97a3526a7 100644 --- a/meilisearch-http/src/data.rs +++ b/meilisearch-http/src/data.rs @@ -9,8 +9,8 @@ use meilisearch_core::{Database, Error as MError, MResult, MainT, UpdateT}; use sha2::Digest; use sysinfo::Pid; +use crate::index_update_callback; use crate::option::Opt; -use crate::routes::index_update_callback; const LAST_UPDATE_KEY: &str = "last-update"; diff --git a/meilisearch-http/src/helpers/authentication.rs b/meilisearch-http/src/helpers/authentication.rs new file mode 100644 index 000000000..de62e22f3 --- /dev/null +++ b/meilisearch-http/src/helpers/authentication.rs @@ -0,0 +1,100 @@ +use std::cell::RefCell; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll}; + +use actix_service::{Service, Transform}; +use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; +use futures::future::{err, ok, Future, Ready}; + +use crate::error::ResponseError; +use crate::Data; + +#[derive(Clone)] +pub enum Authentication { + Public, + Private, + Admin, +} + +impl Transform for Authentication +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = LoggingMiddleware; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ok(LoggingMiddleware { + acl: (*self).clone(), + service: Rc::new(RefCell::new(service)), + }) + } +} + +pub struct LoggingMiddleware { + acl: Authentication, + service: Rc>, +} + +impl Service for LoggingMiddleware +where + S: Service, Error = Error> + 'static, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = Pin>>>; + + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, req: ServiceRequest) -> Self::Future { + let mut svc = self.service.clone(); + let data = req.app_data::().unwrap(); + + if data.api_keys.master.is_none() { + return Box::pin(svc.call(req)); + } + + let auth_header = match req.headers().get("X-Meili-API-Key") { + Some(auth) => match auth.to_str() { + Ok(auth) => auth, + Err(_) => return Box::pin(err(ResponseError::MissingAuthorizationHeader.into())), + }, + None => { + return Box::pin(err(ResponseError::MissingAuthorizationHeader.into())); + } + }; + + 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 { + Box::pin(svc.call(req)) + } else { + Box::pin(err( + ResponseError::InvalidToken(auth_header.to_string()).into() + )) + } + } +} diff --git a/meilisearch-http/src/helpers/mod.rs b/meilisearch-http/src/helpers/mod.rs index eb40f27e6..996333141 100644 --- a/meilisearch-http/src/helpers/mod.rs +++ b/meilisearch-http/src/helpers/mod.rs @@ -1,2 +1,4 @@ +pub mod authentication; pub mod meilisearch; -// pub mod tide; + +pub use authentication::Authentication; diff --git a/meilisearch-http/src/helpers/tide.rs b/meilisearch-http/src/helpers/tide.rs deleted file mode 100644 index f474372f0..000000000 --- a/meilisearch-http/src/helpers/tide.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::error::{ResponseError, SResult}; -use crate::Data; -use meilisearch_core::Index; -use tide::Request; - -pub enum ACL { - Admin, - Private, - Public, -} - -pub trait RequestExt { - fn is_allowed(&self, acl: ACL) -> SResult<()>; - fn url_param(&self, name: &str) -> SResult; - fn index(&self) -> SResult; - fn document_id(&self) -> SResult; -} - -impl RequestExt for Request { - fn is_allowed(&self, acl: ACL) -> SResult<()> { - let user_api_key = self.header("X-Meili-API-Key"); - - if self.state().api_keys.master.is_none() { - return Ok(()) - } - - match acl { - ACL::Admin => { - if user_api_key == self.state().api_keys.master.as_deref() { - return Ok(()); - } - } - ACL::Private => { - if user_api_key == self.state().api_keys.master.as_deref() { - return Ok(()); - } - if user_api_key == self.state().api_keys.private.as_deref() { - return Ok(()); - } - } - ACL::Public => { - if user_api_key == self.state().api_keys.master.as_deref() { - return Ok(()); - } - if user_api_key == self.state().api_keys.private.as_deref() { - return Ok(()); - } - if user_api_key == self.state().api_keys.public.as_deref() { - return Ok(()); - } - } - } - - Err(ResponseError::InvalidToken( - user_api_key.unwrap_or("Need a token").to_owned(), - )) - } - - fn url_param(&self, name: &str) -> SResult { - let param = self - .param::(name) - .map_err(|e| ResponseError::bad_parameter(name, e))?; - Ok(param) - } - - fn index(&self) -> SResult { - let index_uid = self.url_param("index")?; - let index = self - .state() - .db - .open_index(&index_uid) - .ok_or(ResponseError::index_not_found(index_uid))?; - Ok(index) - } - - fn document_id(&self) -> SResult { - let name = self - .param::("document_id") - .map_err(|_| ResponseError::bad_parameter("documentId", "primaryKey"))?; - - Ok(name) - } -} diff --git a/meilisearch-http/src/lib.rs b/meilisearch-http/src/lib.rs index db7fe97c3..77201fd98 100644 --- a/meilisearch-http/src/lib.rs +++ b/meilisearch-http/src/lib.rs @@ -8,3 +8,109 @@ pub mod option; pub mod routes; pub use self::data::Data; +use actix_http::Error; +use actix_service::ServiceFactory; +use actix_web::{dev, web, App}; +use log::error; +use meilisearch_core::ProcessedUpdateResult; + +pub fn create_app( + data: &Data, +) -> App< + impl ServiceFactory< + Config = (), + Request = dev::ServiceRequest, + Response = dev::ServiceResponse, + Error = Error, + InitError = (), + >, + actix_http::body::Body, +> { + App::new() + .app_data(web::Data::new(data.clone())) + .wrap(helpers::Authentication::Public) + .service(routes::load_html) + .service(routes::load_css) + .service(routes::search::search_with_url_query) + .service(routes::search::search_multi_index) + .service(routes::document::get_document) + .service(routes::document::get_all_documents) + .wrap(helpers::Authentication::Private) + .service(routes::index::list_indexes) + .service(routes::index::get_index) + .service(routes::index::create_index) + .service(routes::index::update_index) + .service(routes::index::delete_index) + .service(routes::index::get_update_status) + .service(routes::index::get_all_updates_status) + .service(routes::document::delete_document) + .service(routes::document::add_documents) + .service(routes::document::update_documents) + .service(routes::document::delete_documents) + .service(routes::document::clear_all_documents) + .service(routes::setting::update_all) + .service(routes::setting::get_all) + .service(routes::setting::delete_all) + .service(routes::setting::get_rules) + .service(routes::setting::update_rules) + .service(routes::setting::delete_rules) + .service(routes::setting::get_distinct) + .service(routes::setting::update_distinct) + .service(routes::setting::delete_distinct) + .service(routes::setting::get_searchable) + .service(routes::setting::update_searchable) + .service(routes::setting::delete_searchable) + .service(routes::setting::get_displayed) + .service(routes::setting::update_displayed) + .service(routes::setting::delete_displayed) + .service(routes::setting::get_accept_new_fields) + .service(routes::setting::update_accept_new_fields) + .service(routes::stop_words::get) + .service(routes::stop_words::update) + .service(routes::stop_words::delete) + .service(routes::synonym::get) + .service(routes::synonym::update) + .service(routes::synonym::delete) + .service(routes::stats::index_stats) + .service(routes::stats::get_stats) + .service(routes::stats::get_version) + .service(routes::stats::get_sys_info) + .service(routes::stats::get_sys_info_pretty) + .service(routes::health::get_health) + .service(routes::health::change_healthyness) + .wrap(helpers::Authentication::Admin) + .service(routes::key::list) +} + +pub fn index_update_callback(index_uid: &str, data: &Data, status: ProcessedUpdateResult) { + if status.error.is_some() { + return; + } + + if let Some(index) = data.db.open_index(&index_uid) { + let db = &data.db; + let mut writer = match db.main_write_txn() { + Ok(writer) => writer, + Err(e) => { + error!("Impossible to get write_txn; {}", e); + return; + } + }; + + if let Err(e) = data.compute_stats(&mut writer, &index_uid) { + error!("Impossible to compute stats; {}", e) + } + + if let Err(e) = data.set_last_update(&mut writer) { + error!("Impossible to update last_update; {}", e) + } + + if let Err(e) = index.main.put_updated_at(&mut writer) { + error!("Impossible to update updated_at; {}", e) + } + + if let Err(e) = writer.commit() { + error!("Impossible to get write_txn; {}", e); + } + } +} diff --git a/meilisearch-http/src/main.rs b/meilisearch-http/src/main.rs index 6ea356b63..f802dab33 100644 --- a/meilisearch-http/src/main.rs +++ b/meilisearch-http/src/main.rs @@ -1,13 +1,12 @@ use std::{env, thread}; use actix_cors::Cors; -use actix_web::{middleware, web, App, HttpServer}; +use actix_web::{middleware, HttpServer}; use log::info; use main_error::MainError; use meilisearch_http::data::Data; use meilisearch_http::option::Opt; -use meilisearch_http::routes; -use meilisearch_http::routes::index_update_callback; +use meilisearch_http::{create_app, index_update_callback}; use structopt::StructOpt; mod analytics; @@ -51,7 +50,7 @@ async fn main() -> Result<(), MainError> { print_launch_resume(&opt, &data); HttpServer::new(move || { - App::new() + create_app(&data) .wrap( Cors::new() .send_wildcard() @@ -60,59 +59,6 @@ async fn main() -> Result<(), MainError> { ) .wrap(middleware::Logger::default()) .wrap(middleware::Compress::default()) - .app_data(web::Data::new(data.clone())) - .wrap(routes::Authentication::Public) - .service(routes::load_html) - .service(routes::load_css) - .service(routes::search::search_with_url_query) - .service(routes::search::search_multi_index) - .service(routes::document::get_document) - .service(routes::document::get_all_documents) - .wrap(routes::Authentication::Private) - .service(routes::index::list_indexes) - .service(routes::index::get_index) - .service(routes::index::create_index) - .service(routes::index::update_index) - .service(routes::index::delete_index) - .service(routes::index::get_update_status) - .service(routes::index::get_all_updates_status) - .service(routes::document::delete_document) - .service(routes::document::add_documents) - .service(routes::document::update_documents) - .service(routes::document::delete_documents) - .service(routes::document::clear_all_documents) - .service(routes::setting::update_all) - .service(routes::setting::get_all) - .service(routes::setting::delete_all) - .service(routes::setting::get_rules) - .service(routes::setting::update_rules) - .service(routes::setting::delete_rules) - .service(routes::setting::get_distinct) - .service(routes::setting::update_distinct) - .service(routes::setting::delete_distinct) - .service(routes::setting::get_searchable) - .service(routes::setting::update_searchable) - .service(routes::setting::delete_searchable) - .service(routes::setting::get_displayed) - .service(routes::setting::update_displayed) - .service(routes::setting::delete_displayed) - .service(routes::setting::get_accept_new_fields) - .service(routes::setting::update_accept_new_fields) - .service(routes::stop_words::get) - .service(routes::stop_words::update) - .service(routes::stop_words::delete) - .service(routes::synonym::get) - .service(routes::synonym::update) - .service(routes::synonym::delete) - .service(routes::stats::index_stats) - .service(routes::stats::get_stats) - .service(routes::stats::get_version) - .service(routes::stats::get_sys_info) - .service(routes::stats::get_sys_info_pretty) - .service(routes::health::get_health) - .service(routes::health::change_healthyness) - .wrap(routes::Authentication::Admin) - .service(routes::key::list) }) .bind(opt.http_addr)? .run() diff --git a/meilisearch-http/src/routes/mod.rs b/meilisearch-http/src/routes/mod.rs index 5d92e185f..33b71c684 100644 --- a/meilisearch-http/src/routes/mod.rs +++ b/meilisearch-http/src/routes/mod.rs @@ -1,19 +1,6 @@ -use std::cell::RefCell; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_service::{Service, Transform}; -use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; use actix_web::{get, HttpResponse}; -use futures::future::{err, ok, Future, Ready}; -use log::error; -use meilisearch_core::ProcessedUpdateResult; use serde::{Deserialize, Serialize}; -use crate::error::ResponseError; -use crate::Data; - pub mod document; pub mod health; pub mod index; @@ -54,125 +41,3 @@ pub async fn load_css() -> HttpResponse { .content_type("text/css; charset=utf-8") .body(include_str!("../../public/bulma.min.css").to_string()) } - -pub fn index_update_callback(index_uid: &str, data: &Data, status: ProcessedUpdateResult) { - if status.error.is_some() { - return; - } - - if let Some(index) = data.db.open_index(&index_uid) { - let db = &data.db; - let mut writer = match db.main_write_txn() { - Ok(writer) => writer, - Err(e) => { - error!("Impossible to get write_txn; {}", e); - return; - } - }; - - if let Err(e) = data.compute_stats(&mut writer, &index_uid) { - error!("Impossible to compute stats; {}", e) - } - - if let Err(e) = data.set_last_update(&mut writer) { - error!("Impossible to update last_update; {}", e) - } - - if let Err(e) = index.main.put_updated_at(&mut writer) { - error!("Impossible to update updated_at; {}", e) - } - - if let Err(e) = writer.commit() { - error!("Impossible to get write_txn; {}", e); - } - } -} - -#[derive(Clone)] -pub enum Authentication { - Public, - Private, - Admin, -} - -impl Transform for Authentication -where - S: Service, Error = Error>, - S::Future: 'static, - B: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Transform = LoggingMiddleware; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(LoggingMiddleware { - acl: (*self).clone(), - service: Rc::new(RefCell::new(service)), - }) - } -} - -pub struct LoggingMiddleware { - acl: Authentication, - service: Rc>, -} - -impl Service for LoggingMiddleware -where - S: Service, Error = Error> + 'static, - S::Future: 'static, - B: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = Pin>>>; - - fn poll_ready(&mut self, cx: &mut Context) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, req: ServiceRequest) -> Self::Future { - let mut svc = self.service.clone(); - let data = req.app_data::().unwrap(); - - if data.api_keys.master.is_none() { - return Box::pin(svc.call(req)); - } - - let auth_header = match req.headers().get("X-Meili-API-Key") { - Some(auth) => match auth.to_str() { - Ok(auth) => auth, - Err(_) => return Box::pin(err(ResponseError::MissingAuthorizationHeader.into())), - }, - None => { - return Box::pin(err(ResponseError::MissingAuthorizationHeader.into())); - } - }; - - 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 { - Box::pin(svc.call(req)) - } else { - Box::pin(err( - ResponseError::InvalidToken(auth_header.to_string()).into() - )) - } - } -}