main
use std::sync::Arc;
use tonic::metadata::MetadataMap;
use tonic::service::Interceptor as TonicInterceptor;
use tonic::{Request, Status};
pub const RPC_KEY_HEADER: &str = "x-rpc-key";
pub const CALLER_SERVICE_HEADER: &str = "gitdata-service-id";
/// 门禁通过后挂到请求扩展上的元信息;只用于日志与审计。
#[derive(Debug, Clone)]
pub struct RequestMeta {
pub request_id: ::envelope::RequestId,
pub caller_service: String,
}
impl RequestMeta {
pub fn request_id(&self) -> Option<&str> {
(!self.request_id.0.is_empty()).then_some(self.request_id.as_str())
}
}
pub fn request_id_from(metadata: &MetadataMap) -> Option<String> {
metadata
.get(::envelope::REQUEST_ID_HEADER)
.or_else(|| metadata.get(::envelope::LEGACY_REQUEST_ID_HEADER))
.and_then(|value| value.to_str().ok())
.and_then(::envelope::validate_request_id)
}
/// 共享密钥门禁:校验 `x-rpc-key`,通过后把 `RequestMeta` 挂到请求扩展。
#[derive(Clone)]
pub struct Gate {
key: Arc<str>,
}
impl Gate {
pub fn new(key: impl Into<Arc<str>>) -> Self {
Self { key: key.into() }
}
/// 空密钥会让所有请求通过,所以装配后必须先确认门禁真的能拒绝。
pub fn is_armed(&self) -> bool {
!self.key.trim().is_empty()
}
}
impl TonicInterceptor for Gate {
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
let metadata = request.metadata().clone();
let request_id = ::envelope::context::request_id()
.and_then(|value| ::envelope::validate_request_id(&value))
.or_else(|| request_id_from(&metadata));
if !self.is_armed() {
return Err(unauthorized(
"rpc gate is not configured",
request_id.as_deref(),
));
}
let presented = metadata
.get(RPC_KEY_HEADER)
.and_then(|value| value.to_str().ok())
.map(str::trim)
.unwrap_or_default();
if presented.is_empty()
|| !crypt::constant_time_eq(presented.as_bytes(), self.key.as_bytes())
{
return Err(unauthorized(
"rpc key is missing or invalid",
request_id.as_deref(),
));
}
let meta = RequestMeta {
request_id: ::envelope::RequestId(
request_id.unwrap_or_else(::envelope::new_request_id),
),
caller_service: metadata
.get(CALLER_SERVICE_HEADER)
.and_then(|value| value.to_str().ok())
.map(|value| value.trim().to_owned())
.filter(|value| !value.is_empty())
.unwrap_or_default(),
};
request.extensions_mut().insert(meta);
Ok(request)
}
}
fn unauthorized(message: &str, request_id: Option<&str>) -> Status {
let _ = request_id;
Status::unauthenticated(message)
}
/// 读取门禁挂上的元信息;拦截器已校验密钥,这里只做扩展读取。
pub fn meta<T>(request: &Request<T>) -> Result<RequestMeta, Status> {
request
.extensions()
.get::<RequestMeta>()
.cloned()
.ok_or_else(|| Status::unauthenticated("rpc gate did not attach request metadata"))
}