use tonic::Status;
use crate::CallerIdentity;
use crate::ServerState;
#[cfg(not(feature = "auth"))]
use crate::namespace::grants;
#[cfg(not(feature = "auth"))]
use crate::namespace::grants::GrantWord;
pub(crate) async fn caller_from_metadata(
metadata: &tonic::metadata::MetadataMap,
state: &ServerState,
) -> Result<CallerIdentity, Status> {
if !state.runtime_config().auth.enabled {
return Ok(development_caller_from_metadata(metadata));
}
#[cfg(feature = "auth")]
{
let bearer = metadata
.get("authorization")
.and_then(|value| value.to_str().ok())
.and_then(parse_bearer)
.ok_or_else(|| Status::unauthenticated("missing bearer token"))?;
let Some(cache) = state.jwks_cache() else {
return Err(Status::unauthenticated("invalid bearer token"));
};
return cache
.validate(&bearer)
.await
.map(|claims| claims.caller_identity())
.map_err(|_error| Status::unauthenticated("invalid bearer token"));
}
#[cfg(not(feature = "auth"))]
{
tokio::task::yield_now().await;
Ok(development_token_caller_from_metadata(
metadata,
&state.runtime_config().auth,
))
}
}
fn development_caller_from_metadata(metadata: &tonic::metadata::MetadataMap) -> CallerIdentity {
let subject = metadata
.get("x-aion-subject")
.and_then(|value| value.to_str().ok())
.filter(|value| !value.is_empty())
.unwrap_or("operator");
CallerIdentity::operator(subject)
}
#[cfg(not(feature = "auth"))]
fn grant_metadata_granted(metadata: &tonic::metadata::MetadataMap, grant: &GrantWord) -> bool {
metadata
.get(grant.header())
.and_then(|value| value.to_str().ok())
.is_some_and(|value| value.trim().eq_ignore_ascii_case("true"))
}
#[cfg(not(feature = "auth"))]
fn deploy_metadata_granted(metadata: &tonic::metadata::MetadataMap) -> bool {
grant_metadata_granted(metadata, &grants::DEPLOY)
}
#[cfg(not(feature = "auth"))]
fn assistant_sessions_metadata_granted(metadata: &tonic::metadata::MetadataMap) -> bool {
grant_metadata_granted(metadata, &grants::ASSISTANT_SESSIONS)
}
#[cfg(not(feature = "auth"))]
fn development_token_caller_from_metadata(
metadata: &tonic::metadata::MetadataMap,
auth: &crate::config::AuthConfig,
) -> CallerIdentity {
let subject = metadata
.get("x-aion-subject")
.and_then(|value| value.to_str().ok())
.filter(|value| !value.is_empty());
let namespaces = metadata
.get("x-aion-namespaces")
.and_then(|value| value.to_str().ok())
.map(parse_namespaces)
.unwrap_or_default();
let bearer_token = auth.jwks_url.as_deref().unwrap_or_default();
let expected = format!("Bearer {bearer_token}");
let Some(authorization) = metadata.get("authorization") else {
return CallerIdentity::denied(subject.unwrap_or("anonymous"), "missing bearer token");
};
let authorization = authorization.to_str().ok();
if authorization != Some(expected.as_str()) {
return CallerIdentity::denied(subject.unwrap_or("anonymous"), "invalid bearer token");
}
let Some(subject) = subject else {
return CallerIdentity::denied("anonymous", "missing required metadata: x-aion-subject");
};
CallerIdentity::new(subject, namespaces)
.with_deploy(deploy_metadata_granted(metadata))
.with_assistant_sessions(assistant_sessions_metadata_granted(metadata))
}
#[cfg(feature = "auth")]
fn parse_bearer(value: &str) -> Option<String> {
let token = value.strip_prefix("Bearer ")?.trim();
if token.is_empty() {
return None;
}
Some(token.to_owned())
}
#[cfg(not(feature = "auth"))]
fn parse_namespaces(value: &str) -> Vec<String> {
value
.split(',')
.map(str::trim)
.filter(|namespace| !namespace.is_empty())
.map(str::to_owned)
.collect()
}