mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-25 22:34:28 +01:00
Making it work with index uid patterns
This commit is contained in:
parent
ec7de4bae7
commit
d563ed8a39
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2535,6 +2535,7 @@ dependencies = [
|
|||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"hmac",
|
"hmac",
|
||||||
|
"maplit",
|
||||||
"meilisearch-types",
|
"meilisearch-types",
|
||||||
"rand",
|
"rand",
|
||||||
"roaring",
|
"roaring",
|
||||||
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
base64 = "0.13.1"
|
base64 = "0.13.1"
|
||||||
enum-iterator = "1.1.3"
|
enum-iterator = "1.1.3"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
|
maplit = "1.0.2"
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
roaring = { version = "0.10.0", features = ["serde"] }
|
roaring = { version = "0.10.0", features = ["serde"] }
|
||||||
|
@ -8,6 +8,7 @@ use std::path::Path;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use error::{AuthControllerError, Result};
|
use error::{AuthControllerError, Result};
|
||||||
|
use maplit::hashset;
|
||||||
use meilisearch_types::index_uid_pattern::IndexUidPattern;
|
use meilisearch_types::index_uid_pattern::IndexUidPattern;
|
||||||
use meilisearch_types::keys::{Action, CreateApiKey, Key, PatchApiKey};
|
use meilisearch_types::keys::{Action, CreateApiKey, Key, PatchApiKey};
|
||||||
use meilisearch_types::star_or::StarOr;
|
use meilisearch_types::star_or::StarOr;
|
||||||
@ -75,31 +76,12 @@ impl AuthController {
|
|||||||
search_rules: Option<SearchRules>,
|
search_rules: Option<SearchRules>,
|
||||||
) -> Result<AuthFilter> {
|
) -> Result<AuthFilter> {
|
||||||
let mut filters = AuthFilter::default();
|
let mut filters = AuthFilter::default();
|
||||||
let key = self
|
let key = self.get_key(uid)?;
|
||||||
.store
|
|
||||||
.get_api_key(uid)?
|
|
||||||
.ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string()))?;
|
|
||||||
|
|
||||||
if !key.indexes.iter().any(|i| i == &StarOr::Star) {
|
|
||||||
filters.search_rules = match search_rules {
|
filters.search_rules = match search_rules {
|
||||||
// Intersect search_rules with parent key authorized indexes.
|
Some(search_rules) => search_rules,
|
||||||
Some(search_rules) => SearchRules::Map(
|
None => SearchRules::Set(key.indexes.into_iter().collect()),
|
||||||
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.allow_index_creation = self.is_key_authorized(uid, Action::IndexesAdd, None)?;
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum SearchRules {
|
pub enum SearchRules {
|
||||||
Set(HashSet<String>),
|
Set(HashSet<IndexUidPattern>),
|
||||||
Map(HashMap<String, Option<IndexSearchRules>>),
|
Map(HashMap<IndexUidPattern, Option<IndexSearchRules>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SearchRules {
|
impl Default for SearchRules {
|
||||||
fn default() -> Self {
|
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) => {
|
Self::Set(set) => {
|
||||||
set.contains("*")
|
set.contains("*")
|
||||||
|| set.contains(index)
|
|| set.contains(index)
|
||||||
|| set
|
|| set.iter().any(|pattern| pattern.matches_str(index))
|
||||||
.iter() // We must store the IndexUidPattern in the Set
|
|
||||||
.any(|pattern| IndexUidPattern::new_unchecked(pattern).matches_str(index))
|
|
||||||
}
|
}
|
||||||
Self::Map(map) => {
|
Self::Map(map) => {
|
||||||
map.contains_key("*")
|
map.contains_key("*")
|
||||||
|| map.contains_key(index)
|
|| map.contains_key(index)
|
||||||
|| map
|
|| map.keys().any(|pattern| pattern.matches_str(index))
|
||||||
.keys() // We must store the IndexUidPattern in the Map
|
|
||||||
.any(|pattern| IndexUidPattern::new_unchecked(pattern).matches_str(index))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,21 +193,26 @@ impl SearchRules {
|
|||||||
pub fn get_index_search_rules(&self, index: &str) -> Option<IndexSearchRules> {
|
pub fn get_index_search_rules(&self, index: &str) -> Option<IndexSearchRules> {
|
||||||
match self {
|
match self {
|
||||||
Self::Set(set) => {
|
Self::Set(set) => {
|
||||||
if set.contains("*") || set.contains(index) {
|
if self.is_index_authorized(index) {
|
||||||
Some(IndexSearchRules::default())
|
Some(IndexSearchRules::default())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Map(map) => {
|
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`,
|
/// Return the list of indexes such that `self.is_index_authorized(index) == true`,
|
||||||
/// or `None` if all indexes satisfy this condition.
|
/// or `None` if all indexes satisfy this condition.
|
||||||
pub fn authorized_indexes(&self) -> Option<Vec<String>> {
|
pub fn authorized_indexes(&self) -> Option<Vec<IndexUidPattern>> {
|
||||||
match self {
|
match self {
|
||||||
SearchRules::Set(set) => {
|
SearchRules::Set(set) => {
|
||||||
if set.contains("*") {
|
if set.contains("*") {
|
||||||
@ -250,7 +233,7 @@ impl SearchRules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for SearchRules {
|
impl IntoIterator for SearchRules {
|
||||||
type Item = (String, IndexSearchRules);
|
type Item = (IndexUidPattern, IndexSearchRules);
|
||||||
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use deserr::DeserializeFromValue;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::error::{Code, ErrorCode};
|
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
|
/// An index uid pattern is composed of only ascii alphanumeric characters, - and _, between 1 and 400
|
||||||
/// bytes long and optionally ending with a *.
|
/// bytes long and optionally ending with a *.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, DeserializeFromValue, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
#[deserr(from(&String) = FromStr::from_str -> IndexUidPatternFormatError)]
|
||||||
pub struct IndexUidPattern(
|
pub struct IndexUidPattern(String);
|
||||||
#[cfg_attr(feature = "test-traits", proptest(regex("[a-zA-Z0-9_-]{1,400}\\*?")))] String,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl IndexUidPattern {
|
impl IndexUidPattern {
|
||||||
pub fn new_unchecked(s: impl AsRef<str>) -> Self {
|
pub fn new_unchecked(s: impl AsRef<str>) -> Self {
|
||||||
Self(s.as_ref().to_string())
|
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.
|
/// Returns wether this index uid matches this index uid pattern.
|
||||||
pub fn matches(&self, uid: &IndexUid) -> bool {
|
pub fn matches(&self, uid: &IndexUid) -> bool {
|
||||||
self.matches_str(uid.as_str())
|
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;
|
type Target = str;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@ -42,6 +53,12 @@ impl std::ops::Deref for IndexUidPattern {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for IndexUidPattern {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for IndexUidPattern {
|
impl TryFrom<String> for IndexUidPattern {
|
||||||
type Error = IndexUidPatternFormatError;
|
type Error = IndexUidPatternFormatError;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ pub struct CreateApiKey {
|
|||||||
#[deserr(error = DeserrError<InvalidApiKeyActions>)]
|
#[deserr(error = DeserrError<InvalidApiKeyActions>)]
|
||||||
pub actions: Vec<Action>,
|
pub actions: Vec<Action>,
|
||||||
#[deserr(error = DeserrError<InvalidApiKeyIndexes>)]
|
#[deserr(error = DeserrError<InvalidApiKeyIndexes>)]
|
||||||
pub indexes: Vec<StarOr<IndexUidPattern>>,
|
pub indexes: Vec<IndexUidPattern>,
|
||||||
#[deserr(error = DeserrError<InvalidApiKeyExpiresAt>, default = None, from(&String) = parse_expiration_date -> TakeErrorMessage<ParseOffsetDateTimeError>)]
|
#[deserr(error = DeserrError<InvalidApiKeyExpiresAt>, default = None, from(&String) = parse_expiration_date -> TakeErrorMessage<ParseOffsetDateTimeError>)]
|
||||||
pub expires_at: Option<OffsetDateTime>,
|
pub expires_at: Option<OffsetDateTime>,
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ pub struct Key {
|
|||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub uid: KeyId,
|
pub uid: KeyId,
|
||||||
pub actions: Vec<Action>,
|
pub actions: Vec<Action>,
|
||||||
pub indexes: Vec<StarOr<IndexUidPattern>>,
|
pub indexes: Vec<IndexUidPattern>,
|
||||||
#[serde(with = "time::serde::rfc3339::option")]
|
#[serde(with = "time::serde::rfc3339::option")]
|
||||||
pub expires_at: Option<OffsetDateTime>,
|
pub expires_at: Option<OffsetDateTime>,
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
#[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()),
|
description: Some("Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend".to_string()),
|
||||||
uid,
|
uid,
|
||||||
actions: vec![Action::All],
|
actions: vec![Action::All],
|
||||||
indexes: vec![StarOr::Star],
|
indexes: vec![IndexUidPattern::all()],
|
||||||
expires_at: None,
|
expires_at: None,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
@ -142,7 +142,7 @@ impl Key {
|
|||||||
description: Some("Use it to search from the frontend".to_string()),
|
description: Some("Use it to search from the frontend".to_string()),
|
||||||
uid,
|
uid,
|
||||||
actions: vec![Action::Search],
|
actions: vec![Action::Search],
|
||||||
indexes: vec![StarOr::Star],
|
indexes: vec![IndexUidPattern::all()],
|
||||||
expires_at: None,
|
expires_at: None,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
|
@ -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
|
None
|
||||||
|
Loading…
Reference in New Issue
Block a user