Compare commits

...

1 Commits

Author SHA1 Message Date
YoEight
b0994e9958 Render auth optional on the metrics endpoint 2025-12-18 08:39:55 -05:00
3 changed files with 82 additions and 20 deletions

View File

@@ -27,7 +27,7 @@ pub(crate) struct FeatureData {
network: Arc<RwLock<Network>>, network: Arc<RwLock<Network>>,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, Default)]
pub struct RoFeatures { pub struct RoFeatures {
runtime: RuntimeTogglableFeatures, runtime: RuntimeTogglableFeatures,
} }

View File

@@ -1,17 +1,18 @@
mod error; mod error;
use std::marker::PhantomData; use actix_http::Payload;
use std::ops::Deref;
use std::pin::Pin;
use actix_web::http::header::AUTHORIZATION; use actix_web::http::header::AUTHORIZATION;
use actix_web::web::Data; use actix_web::web::Data;
use actix_web::FromRequest; use actix_web::{FromRequest, HttpRequest};
pub use error::AuthenticationError; pub use error::AuthenticationError;
use futures::future::err; use futures::future::err;
use futures::Future; use futures::Future;
use futures_util::future::ok;
use meilisearch_auth::{AuthController, AuthFilter}; use meilisearch_auth::{AuthController, AuthFilter};
use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::error::{Code, ResponseError};
use std::marker::PhantomData;
use std::ops::Deref;
use std::pin::Pin;
use self::policies::AuthError; use self::policies::AuthError;
@@ -114,6 +115,70 @@ impl<P: Policy + 'static, D: 'static + Clone> FromRequest for GuardedData<P, D>
} }
} }
pub struct OptionallyGuardedData<P, D> {
data: D,
filters: AuthFilter,
_marker: PhantomData<P>,
}
impl<P, D> OptionallyGuardedData<P, D> {
pub fn filters(&self) -> &AuthFilter {
&self.filters
}
}
impl<P, D> Deref for OptionallyGuardedData<P, D> {
type Target = D;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<P: Policy + 'static, D: 'static + Clone> FromRequest for OptionallyGuardedData<P, D> {
type Error = ResponseError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let data = if let Some(d) = req.app_data::<D>().cloned() {
d
} else {
return Box::pin(err(AuthenticationError::IrretrievableState.into()));
};
match req.app_data::<Data<AuthController>>().cloned() {
Some(auth) => match extract_token_from_request(req) {
Ok(Some(token)) => {
let token = token.to_owned();
let index = req.match_info().get("index_uid").map(String::from);
// TODO: find a less hardcoded way?
Box::pin(async move {
let guarded =
GuardedData::<P, D>::auth_bearer(auth, token, index, Some(data))
.await?;
Ok(OptionallyGuardedData {
data: guarded.data,
filters: guarded.filters,
_marker: PhantomData,
})
})
}
Ok(None) => Box::pin(ok(OptionallyGuardedData {
data,
filters: AuthFilter::default(),
_marker: PhantomData,
})),
Err(e) => Box::pin(err(e.into())),
},
None => Box::pin(err(AuthenticationError::IrretrievableState.into())),
}
}
}
pub fn extract_token_from_request( pub fn extract_token_from_request(
req: &actix_web::HttpRequest, req: &actix_web::HttpRequest,
) -> Result<Option<&str>, AuthenticationError> { ) -> Result<Option<&str>, AuthenticationError> {

View File

@@ -11,7 +11,7 @@ use time::OffsetDateTime;
use utoipa::OpenApi; use utoipa::OpenApi;
use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::policies::ActionPolicy;
use crate::extractors::authentication::{AuthenticationError, GuardedData}; use crate::extractors::authentication::OptionallyGuardedData;
use crate::routes::create_all_stats; use crate::routes::create_all_stats;
use crate::search_queue::SearchQueue; use crate::search_queue::SearchQueue;
@@ -123,20 +123,15 @@ meilisearch_used_db_size_bytes 409600
) )
)] )]
pub async fn get_metrics( pub async fn get_metrics(
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>, index_scheduler: OptionallyGuardedData<
ActionPolicy<{ actions::METRICS_GET }>,
Data<IndexScheduler>,
>,
auth_controller: Data<AuthController>, auth_controller: Data<AuthController>,
search_queue: web::Data<SearchQueue>, search_queue: web::Data<SearchQueue>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
index_scheduler.features().check_metrics()?; index_scheduler.features().check_metrics()?;
let auth_filters = index_scheduler.filters(); let auth_filters = index_scheduler.filters();
if !auth_filters.all_indexes_authorized() {
let mut error = ResponseError::from(AuthenticationError::InvalidToken);
error
.message
.push_str(" The API key for the `/metrics` route must allow access to all indexes.");
return Err(error);
}
let response = create_all_stats((*index_scheduler).clone(), auth_controller, auth_filters)?; let response = create_all_stats((*index_scheduler).clone(), auth_controller, auth_filters)?;
crate::metrics::MEILISEARCH_DB_SIZE_BYTES.set(response.database_size as i64); crate::metrics::MEILISEARCH_DB_SIZE_BYTES.set(response.database_size as i64);
@@ -148,10 +143,12 @@ pub async fn get_metrics(
crate::metrics::MEILISEARCH_SEARCHES_WAITING_TO_BE_PROCESSED crate::metrics::MEILISEARCH_SEARCHES_WAITING_TO_BE_PROCESSED
.set(search_queue.searches_waiting() as i64); .set(search_queue.searches_waiting() as i64);
for (index, value) in response.indexes.iter() { if auth_filters.all_indexes_authorized() {
crate::metrics::MEILISEARCH_INDEX_DOCS_COUNT for (index, value) in response.indexes.iter() {
.with_label_values(&[index]) crate::metrics::MEILISEARCH_INDEX_DOCS_COUNT
.set(value.number_of_documents as i64); .with_label_values(&[index])
.set(value.number_of_documents as i64);
}
} }
for (kind, value) in index_scheduler.get_stats()? { for (kind, value) in index_scheduler.get_stats()? {