Skip to content
Clone
eba4fd1
98 lines 3.0 KB Rust
use tonic::service::Interceptor;
use tonic::{Request, Status};
use uuid::Uuid;
use admin::share::roles::{AdminActor, AdminRole};
pub const TRUSTED_KEY_HEADER: &str = "x-trusted-key";
pub const ADMIN_ID_HEADER: &str = "x-admin-id";
pub const ADMIN_USERNAME_HEADER: &str = "x-admin-username";
pub const ADMIN_DISPLAY_NAME_HEADER: &str = "x-admin-display-name";
pub const ADMIN_ROLE_HEADER: &str = "x-admin-role";
#[derive(Debug, Clone)]
pub struct TrustedPrincipal {
pub id: Uuid,
pub username: String,
pub display_name: String,
pub role: AdminRole,
}
impl TrustedPrincipal {
pub fn actor(&self) -> AdminActor {
AdminActor {
id: self.id,
username: self.username.clone(),
display_name: self.display_name.clone(),
role: self.role,
must_change_password: false,
}
}
}
#[derive(Clone)]
pub struct TrustedBackend {
keys: Vec<String>,
}
impl TrustedBackend {
pub fn new(keys: Vec<String>) -> Self {
Self { keys }
}
}
impl Interceptor for TrustedBackend {
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
if self.keys.is_empty() {
return Err(Status::unauthenticated("no trusted keys are configured"));
}
let presented = header(request.metadata(), TRUSTED_KEY_HEADER).unwrap_or_default();
if !self
.keys
.iter()
.any(|key| !key.is_empty() && key == &presented)
{
return Err(Status::unauthenticated("invalid trusted key"));
}
let username =
header(request.metadata(), ADMIN_USERNAME_HEADER).unwrap_or("console".to_string());
let display_name = header(request.metadata(), ADMIN_DISPLAY_NAME_HEADER)
.unwrap_or_else(|| username.clone());
let role = parse_role(header(request.metadata(), ADMIN_ROLE_HEADER).as_deref());
let id = header(request.metadata(), ADMIN_ID_HEADER)
.and_then(|value| Uuid::parse_str(&value).ok())
.unwrap_or_else(Uuid::nil);
request.extensions_mut().insert(TrustedPrincipal {
id,
username,
display_name,
role,
});
Ok(request)
}
}
pub fn principal<T>(request: &Request<T>) -> Result<TrustedPrincipal, Status> {
request
.extensions()
.get::<TrustedPrincipal>()
.cloned()
.ok_or_else(|| Status::unauthenticated("missing trusted principal"))
}
fn header(metadata: &tonic::metadata::MetadataMap, name: &str) -> Option<String> {
metadata
.get(name)
.and_then(|value| value.to_str().ok())
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
}
fn parse_role(value: Option<&str>) -> AdminRole {
match value.unwrap_or_default().to_ascii_lowercase().as_str() {
"admin" | "super_admin" | "superadmin" => AdminRole::SuperAdmin,
"operator" => AdminRole::Operator,
_ => AdminRole::Viewer,
}
}