Authentication: is_index_authorized takes into account API key indexes even with a tenant token

This commit is contained in:
Louis Dureuil 2023-02-20 09:25:29 +01:00
parent 4b65851793
commit c8c5944094
No known key found for this signature in database
7 changed files with 56 additions and 35 deletions

View File

@ -88,9 +88,11 @@ impl AuthController {
let mut filters = AuthFilter::default(); let mut filters = AuthFilter::default();
let key = self.get_key(uid)?; let key = self.get_key(uid)?;
filters.key_authorized_indexes = SearchRules::Set(key.indexes.into_iter().collect());
filters.search_rules = match search_rules { filters.search_rules = match search_rules {
Some(search_rules) => search_rules, Some(search_rules) => search_rules,
None => SearchRules::Set(key.indexes.into_iter().collect()), None => filters.key_authorized_indexes.clone(),
}; };
filters.allow_index_creation = self.is_key_authorized(uid, Action::IndexesAdd, None)?; filters.allow_index_creation = self.is_key_authorized(uid, Action::IndexesAdd, None)?;
@ -160,13 +162,42 @@ impl AuthController {
} }
pub struct AuthFilter { pub struct AuthFilter {
pub search_rules: SearchRules, search_rules: SearchRules,
key_authorized_indexes: SearchRules,
pub allow_index_creation: bool, pub allow_index_creation: bool,
} }
impl Default for AuthFilter { impl Default for AuthFilter {
fn default() -> Self { fn default() -> Self {
Self { search_rules: SearchRules::default(), allow_index_creation: true } Self {
search_rules: SearchRules::default(),
key_authorized_indexes: SearchRules::default(),
allow_index_creation: true,
}
}
}
impl AuthFilter {
pub fn is_index_authorized(&self, index: &str) -> bool {
self.key_authorized_indexes.is_index_authorized(index)
&& self.search_rules.is_index_authorized(index)
}
pub fn get_index_search_rules(&self, index: &str) -> Option<IndexSearchRules> {
if !self.is_index_authorized(index) {
return None;
}
self.search_rules.get_index_search_rules(index)
}
/// Return the list of indexes such that `self.is_index_authorized(index) == true`,
/// or `None` if all indexes satisfy this condition.
///
/// FIXME: this works only when there are no tenant tokens, otherwise it ignores the rules of the API key.
///
/// It is better to use `is_index_authorized` when possible.
pub fn authorized_indexes(&self) -> Option<Vec<IndexUidPattern>> {
self.search_rules.authorized_indexes()
} }
} }
@ -185,7 +216,7 @@ impl Default for SearchRules {
} }
impl SearchRules { impl SearchRules {
pub fn is_index_authorized(&self, index: &str) -> bool { fn is_index_authorized(&self, index: &str) -> bool {
match self { match self {
Self::Set(set) => { Self::Set(set) => {
set.contains("*") set.contains("*")
@ -200,7 +231,7 @@ impl SearchRules {
} }
} }
pub fn get_index_search_rules(&self, index: &str) -> Option<IndexSearchRules> { fn get_index_search_rules(&self, index: &str) -> Option<IndexSearchRules> {
match self { match self {
Self::Set(_) => { Self::Set(_) => {
if self.is_index_authorized(index) { if self.is_index_authorized(index) {
@ -221,7 +252,7 @@ impl SearchRules {
/// 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<IndexUidPattern>> { fn authorized_indexes(&self) -> Option<Vec<IndexUidPattern>> {
match self { match self {
SearchRules::Set(set) => { SearchRules::Set(set) => {
if set.contains("*") { if set.contains("*") {

View File

@ -9,7 +9,7 @@ use actix_web::HttpRequest;
use byte_unit::Byte; use byte_unit::Byte;
use http::header::CONTENT_TYPE; use http::header::CONTENT_TYPE;
use index_scheduler::IndexScheduler; use index_scheduler::IndexScheduler;
use meilisearch_auth::{AuthController, SearchRules}; use meilisearch_auth::{AuthController, AuthFilter};
use meilisearch_types::InstanceUid; use meilisearch_types::InstanceUid;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
@ -401,7 +401,7 @@ impl Segment {
auth_controller: AuthController, auth_controller: AuthController,
) { ) {
if let Ok(stats) = if let Ok(stats) =
create_all_stats(index_scheduler.into(), auth_controller, &SearchRules::default()) create_all_stats(index_scheduler.into(), auth_controller, &AuthFilter::default())
{ {
// Replace the version number with the prototype name if any. // Replace the version number with the prototype name if any.
let version = if let Some(prototype) = crate::prototype_name() { let version = if let Some(prototype) = crate::prototype_name() {

View File

@ -89,11 +89,11 @@ pub async fn list_indexes(
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>,
paginate: AwebQueryParameter<ListIndexes, DeserrQueryParamError>, paginate: AwebQueryParameter<ListIndexes, DeserrQueryParamError>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let search_rules = &index_scheduler.filters().search_rules; let filters = index_scheduler.filters();
let indexes: Vec<_> = index_scheduler.indexes()?; let indexes: Vec<_> = index_scheduler.indexes()?;
let indexes = indexes let indexes = indexes
.into_iter() .into_iter()
.filter(|(name, _)| search_rules.is_index_authorized(name)) .filter(|(name, _)| filters.is_index_authorized(name))
.map(|(name, index)| IndexView::new(name, &index)) .map(|(name, index)| IndexView::new(name, &index))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
@ -120,7 +120,8 @@ pub async fn create_index(
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let IndexCreateRequest { primary_key, uid } = body.into_inner(); let IndexCreateRequest { primary_key, uid } = body.into_inner();
let allow_index_creation = index_scheduler.filters().search_rules.is_index_authorized(&uid); // FIXME: allow_index_creation?
let allow_index_creation = index_scheduler.filters().is_index_authorized(&uid);
if allow_index_creation { if allow_index_creation {
analytics.publish( analytics.publish(
"Index Created".to_string(), "Index Created".to_string(),

View File

@ -159,9 +159,7 @@ pub async fn search_with_url_query(
let mut query: SearchQuery = params.into_inner().into(); let mut query: SearchQuery = params.into_inner().into();
// Tenant token search_rules. // Tenant token search_rules.
if let Some(search_rules) = if let Some(search_rules) = index_scheduler.filters().get_index_search_rules(&index_uid) {
index_scheduler.filters().search_rules.get_index_search_rules(&index_uid)
{
add_search_rules(&mut query, search_rules); add_search_rules(&mut query, search_rules);
} }
@ -193,9 +191,7 @@ pub async fn search_with_post(
debug!("search called with params: {:?}", query); debug!("search called with params: {:?}", query);
// Tenant token search_rules. // Tenant token search_rules.
if let Some(search_rules) = if let Some(search_rules) = index_scheduler.filters().get_index_search_rules(&index_uid) {
index_scheduler.filters().search_rules.get_index_search_rules(&index_uid)
{
add_search_rules(&mut query, search_rules); add_search_rules(&mut query, search_rules);
} }

View File

@ -237,10 +237,9 @@ async fn get_stats(
analytics: web::Data<dyn Analytics>, analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
analytics.publish("Stats Seen".to_string(), json!({ "per_index_uid": false }), Some(&req)); analytics.publish("Stats Seen".to_string(), json!({ "per_index_uid": false }), Some(&req));
let search_rules = &index_scheduler.filters().search_rules; let filters = index_scheduler.filters();
let stats = let stats = create_all_stats((*index_scheduler).clone(), (*auth_controller).clone(), filters)?;
create_all_stats((*index_scheduler).clone(), (*auth_controller).clone(), search_rules)?;
debug!("returns: {:?}", stats); debug!("returns: {:?}", stats);
Ok(HttpResponse::Ok().json(stats)) Ok(HttpResponse::Ok().json(stats))
@ -249,19 +248,19 @@ async fn get_stats(
pub fn create_all_stats( pub fn create_all_stats(
index_scheduler: Data<IndexScheduler>, index_scheduler: Data<IndexScheduler>,
auth_controller: AuthController, auth_controller: AuthController,
search_rules: &meilisearch_auth::SearchRules, filters: &meilisearch_auth::AuthFilter,
) -> Result<Stats, ResponseError> { ) -> Result<Stats, ResponseError> {
let mut last_task: Option<OffsetDateTime> = None; let mut last_task: Option<OffsetDateTime> = None;
let mut indexes = BTreeMap::new(); let mut indexes = BTreeMap::new();
let mut database_size = 0; let mut database_size = 0;
let processing_task = index_scheduler.get_tasks_from_authorized_indexes( let processing_task = index_scheduler.get_tasks_from_authorized_indexes(
Query { statuses: Some(vec![Status::Processing]), limit: Some(1), ..Query::default() }, Query { statuses: Some(vec![Status::Processing]), limit: Some(1), ..Query::default() },
search_rules.authorized_indexes(), filters.authorized_indexes(),
)?; )?;
// accumulate the size of each indexes // accumulate the size of each indexes
let processing_index = processing_task.first().and_then(|task| task.index_uid()); let processing_index = processing_task.first().and_then(|task| task.index_uid());
for (name, index) in index_scheduler.indexes()? { for (name, index) in index_scheduler.indexes()? {
if !search_rules.is_index_authorized(&name) { if !filters.is_index_authorized(&name) {
continue; continue;
} }

View File

@ -42,7 +42,7 @@ pub async fn swap_indexes(
}), }),
Some(&req), Some(&req),
); );
let search_rules = &index_scheduler.filters().search_rules; let filters = index_scheduler.filters();
let mut swaps = vec![]; let mut swaps = vec![];
for SwapIndexesPayload { indexes } in params.into_iter() { for SwapIndexesPayload { indexes } in params.into_iter() {
@ -53,7 +53,7 @@ pub async fn swap_indexes(
return Err(MeilisearchHttpError::SwapIndexPayloadWrongLength(indexes).into()); return Err(MeilisearchHttpError::SwapIndexPayloadWrongLength(indexes).into());
} }
}; };
if !search_rules.is_index_authorized(lhs) || !search_rules.is_index_authorized(rhs) { if !filters.is_index_authorized(lhs) || !filters.is_index_authorized(rhs) {
return Err(AuthenticationError::InvalidToken.into()); return Err(AuthenticationError::InvalidToken.into());
} }
swaps.push(IndexSwap { indexes: (lhs.to_string(), rhs.to_string()) }); swaps.push(IndexSwap { indexes: (lhs.to_string(), rhs.to_string()) });

View File

@ -319,7 +319,7 @@ async fn cancel_tasks(
let tasks = index_scheduler.get_task_ids_from_authorized_indexes( let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
&index_scheduler.read_txn()?, &index_scheduler.read_txn()?,
&query, &query,
&index_scheduler.filters().search_rules.authorized_indexes(), &index_scheduler.filters().authorized_indexes(),
)?; )?;
let task_cancelation = let task_cancelation =
KindWithContent::TaskCancelation { query: format!("?{}", req.query_string()), tasks }; KindWithContent::TaskCancelation { query: format!("?{}", req.query_string()), tasks };
@ -364,7 +364,7 @@ async fn delete_tasks(
let tasks = index_scheduler.get_task_ids_from_authorized_indexes( let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
&index_scheduler.read_txn()?, &index_scheduler.read_txn()?,
&query, &query,
&index_scheduler.filters().search_rules.authorized_indexes(), &index_scheduler.filters().authorized_indexes(),
)?; )?;
let task_deletion = let task_deletion =
KindWithContent::TaskDeletion { query: format!("?{}", req.query_string()), tasks }; KindWithContent::TaskDeletion { query: format!("?{}", req.query_string()), tasks };
@ -398,10 +398,7 @@ async fn get_tasks(
let query = params.into_query(); let query = params.into_query();
let mut tasks_results: Vec<TaskView> = index_scheduler let mut tasks_results: Vec<TaskView> = index_scheduler
.get_tasks_from_authorized_indexes( .get_tasks_from_authorized_indexes(query, index_scheduler.filters().authorized_indexes())?
query,
index_scheduler.filters().search_rules.authorized_indexes(),
)?
.into_iter() .into_iter()
.map(|t| TaskView::from_task(&t)) .map(|t| TaskView::from_task(&t))
.collect(); .collect();
@ -440,10 +437,7 @@ async fn get_task(
let query = index_scheduler::Query { uids: Some(vec![task_uid]), ..Query::default() }; let query = index_scheduler::Query { uids: Some(vec![task_uid]), ..Query::default() };
if let Some(task) = index_scheduler if let Some(task) = index_scheduler
.get_tasks_from_authorized_indexes( .get_tasks_from_authorized_indexes(query, index_scheduler.filters().authorized_indexes())?
query,
index_scheduler.filters().search_rules.authorized_indexes(),
)?
.first() .first()
{ {
let task_view = TaskView::from_task(task); let task_view = TaskView::from_task(task);