From d563ed8a39a81b4aebb0899be3963100bac66aba Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Wed, 25 Jan 2023 17:22:32 +0100 Subject: [PATCH] Making it work with index uid patterns --- Cargo.lock | 1 + meilisearch-auth/Cargo.toml | 1 + meilisearch-auth/src/lib.rs | 57 +++++++------------ meilisearch-types/src/index_uid_pattern.rs | 29 ++++++++-- meilisearch-types/src/keys.rs | 8 +-- .../src/extractors/authentication/mod.rs | 5 +- 6 files changed, 53 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd7d828da..0fc88e802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,6 +2535,7 @@ dependencies = [ "base64 0.13.1", "enum-iterator", "hmac", + "maplit", "meilisearch-types", "rand", "roaring", diff --git a/meilisearch-auth/Cargo.toml b/meilisearch-auth/Cargo.toml index 383be69cf..a42cbae02 100644 --- a/meilisearch-auth/Cargo.toml +++ b/meilisearch-auth/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" base64 = "0.13.1" enum-iterator = "1.1.3" hmac = "0.12.1" +maplit = "1.0.2" meilisearch-types = { path = "../meilisearch-types" } rand = "0.8.5" roaring = { version = "0.10.0", features = ["serde"] } diff --git a/meilisearch-auth/src/lib.rs b/meilisearch-auth/src/lib.rs index c81f9f20b..287dda0ce 100644 --- a/meilisearch-auth/src/lib.rs +++ b/meilisearch-auth/src/lib.rs @@ -8,6 +8,7 @@ use std::path::Path; use std::sync::Arc; use error::{AuthControllerError, Result}; +use maplit::hashset; use meilisearch_types::index_uid_pattern::IndexUidPattern; use meilisearch_types::keys::{Action, CreateApiKey, Key, PatchApiKey}; use meilisearch_types::star_or::StarOr; @@ -75,31 +76,12 @@ impl AuthController { search_rules: Option, ) -> Result { let mut filters = AuthFilter::default(); - let key = self - .store - .get_api_key(uid)? - .ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string()))?; + let key = self.get_key(uid)?; - if !key.indexes.iter().any(|i| i == &StarOr::Star) { - filters.search_rules = match search_rules { - // Intersect search_rules with parent key authorized indexes. - Some(search_rules) => SearchRules::Map( - key.indexes - .into_iter() - .filter_map(|index| { - search_rules.get_index_search_rules(index.deref()).map( - |index_search_rules| { - (String::from(index), Some(index_search_rules)) - }, - ) - }) - .collect(), - ), - None => SearchRules::Set(key.indexes.into_iter().map(String::from).collect()), - }; - } else if let Some(search_rules) = search_rules { - filters.search_rules = search_rules; - } + filters.search_rules = match search_rules { + Some(search_rules) => search_rules, + None => SearchRules::Set(key.indexes.into_iter().collect()), + }; filters.allow_index_creation = self.is_key_authorized(uid, Action::IndexesAdd, None)?; @@ -182,13 +164,13 @@ impl Default for AuthFilter { #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] pub enum SearchRules { - Set(HashSet), - Map(HashMap>), + Set(HashSet), + Map(HashMap>), } impl Default for SearchRules { fn default() -> Self { - Self::Set(Some("*".to_string()).into_iter().collect()) + Self::Set(hashset! { IndexUidPattern::all() }) } } @@ -198,16 +180,12 @@ impl SearchRules { Self::Set(set) => { set.contains("*") || set.contains(index) - || set - .iter() // We must store the IndexUidPattern in the Set - .any(|pattern| IndexUidPattern::new_unchecked(pattern).matches_str(index)) + || set.iter().any(|pattern| pattern.matches_str(index)) } Self::Map(map) => { map.contains_key("*") || map.contains_key(index) - || map - .keys() // We must store the IndexUidPattern in the Map - .any(|pattern| IndexUidPattern::new_unchecked(pattern).matches_str(index)) + || map.keys().any(|pattern| pattern.matches_str(index)) } } } @@ -215,21 +193,26 @@ impl SearchRules { pub fn get_index_search_rules(&self, index: &str) -> Option { match self { Self::Set(set) => { - if set.contains("*") || set.contains(index) { + if self.is_index_authorized(index) { Some(IndexSearchRules::default()) } else { None } } Self::Map(map) => { - map.get(index).or_else(|| map.get("*")).map(|isr| isr.clone().unwrap_or_default()) + // We must take the most retrictive rule of this index uid patterns set of rules. + map.iter() + .filter(|(pattern, _)| pattern.matches_str(index)) + .max_by_key(|(pattern, _)| (pattern.is_exact(), pattern.len())) + .map(|(_, rule)| rule.clone()) + .flatten() } } } /// Return the list of indexes such that `self.is_index_authorized(index) == true`, /// or `None` if all indexes satisfy this condition. - pub fn authorized_indexes(&self) -> Option> { + pub fn authorized_indexes(&self) -> Option> { match self { SearchRules::Set(set) => { if set.contains("*") { @@ -250,7 +233,7 @@ impl SearchRules { } impl IntoIterator for SearchRules { - type Item = (String, IndexSearchRules); + type Item = (IndexUidPattern, IndexSearchRules); type IntoIter = Box>; fn into_iter(self) -> Self::IntoIter { diff --git a/meilisearch-types/src/index_uid_pattern.rs b/meilisearch-types/src/index_uid_pattern.rs index 8cb50fee9..bc12da351 100644 --- a/meilisearch-types/src/index_uid_pattern.rs +++ b/meilisearch-types/src/index_uid_pattern.rs @@ -1,7 +1,10 @@ +use std::borrow::Borrow; use std::error::Error; use std::fmt; +use std::ops::Deref; use std::str::FromStr; +use deserr::DeserializeFromValue; use serde::{Deserialize, Serialize}; use crate::error::{Code, ErrorCode}; @@ -9,17 +12,25 @@ use crate::index_uid::{IndexUid, IndexUidFormatError}; /// An index uid pattern is composed of only ascii alphanumeric characters, - and _, between 1 and 400 /// bytes long and optionally ending with a *. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))] -pub struct IndexUidPattern( - #[cfg_attr(feature = "test-traits", proptest(regex("[a-zA-Z0-9_-]{1,400}\\*?")))] String, -); +#[derive(Serialize, Deserialize, DeserializeFromValue, Debug, Clone, PartialEq, Eq, Hash)] +#[deserr(from(&String) = FromStr::from_str -> IndexUidPatternFormatError)] +pub struct IndexUidPattern(String); impl IndexUidPattern { pub fn new_unchecked(s: impl AsRef) -> Self { Self(s.as_ref().to_string()) } + /// Matches any index name. + pub fn all() -> Self { + IndexUidPattern::from_str("*").unwrap() + } + + /// Returns `true` if the pattern matches a specific index name. + pub fn is_exact(&self) -> bool { + !self.0.ends_with('*') + } + /// Returns wether this index uid matches this index uid pattern. pub fn matches(&self, uid: &IndexUid) -> bool { self.matches_str(uid.as_str()) @@ -34,7 +45,7 @@ impl IndexUidPattern { } } -impl std::ops::Deref for IndexUidPattern { +impl Deref for IndexUidPattern { type Target = str; fn deref(&self) -> &Self::Target { @@ -42,6 +53,12 @@ impl std::ops::Deref for IndexUidPattern { } } +impl Borrow for IndexUidPattern { + fn borrow(&self) -> &str { + &self.0 + } +} + impl TryFrom for IndexUidPattern { type Error = IndexUidPatternFormatError; diff --git a/meilisearch-types/src/keys.rs b/meilisearch-types/src/keys.rs index 50afa755c..a9e2e0889 100644 --- a/meilisearch-types/src/keys.rs +++ b/meilisearch-types/src/keys.rs @@ -47,7 +47,7 @@ pub struct CreateApiKey { #[deserr(error = DeserrError)] pub actions: Vec, #[deserr(error = DeserrError)] - pub indexes: Vec>, + pub indexes: Vec, #[deserr(error = DeserrError, default = None, from(&String) = parse_expiration_date -> TakeErrorMessage)] pub expires_at: Option, } @@ -109,7 +109,7 @@ pub struct Key { pub name: Option, pub uid: KeyId, pub actions: Vec, - pub indexes: Vec>, + pub indexes: Vec, #[serde(with = "time::serde::rfc3339::option")] pub expires_at: Option, #[serde(with = "time::serde::rfc3339")] @@ -127,7 +127,7 @@ impl Key { description: Some("Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend".to_string()), uid, actions: vec![Action::All], - indexes: vec![StarOr::Star], + indexes: vec![IndexUidPattern::all()], expires_at: None, created_at: now, updated_at: now, @@ -142,7 +142,7 @@ impl Key { description: Some("Use it to search from the frontend".to_string()), uid, actions: vec![Action::Search], - indexes: vec![StarOr::Star], + indexes: vec![IndexUidPattern::all()], expires_at: None, created_at: now, updated_at: now, diff --git a/meilisearch/src/extractors/authentication/mod.rs b/meilisearch/src/extractors/authentication/mod.rs index 8944b60d3..f1efdf9aa 100644 --- a/meilisearch/src/extractors/authentication/mod.rs +++ b/meilisearch/src/extractors/authentication/mod.rs @@ -230,7 +230,10 @@ pub mod policies { } } - return auth.get_key_filters(uid, Some(data.claims.search_rules)).ok(); + match auth.get_key_filters(uid, Some(data.claims.search_rules)) { + Ok(auth) if auth.search_rules.is_index_authorized() => Some(auth), + _ => None, + } } None