use std::net::IpAddr;
use super::Session;
use crate::observe::{HttpRequestEventCtx, NetworkBytesEventCtx, TenantIdentity};
fn session_user(sess: &Session) -> Option<String> {
if sess.au.is_anon() {
None
} else if sess.au.is_record() {
Some("<record>".to_owned())
} else {
Some(sess.au.id().to_owned())
}
}
fn session_client_ip(sess: &Session) -> Option<IpAddr> {
sess.ip.as_deref().and_then(|raw| raw.parse::<IpAddr>().ok())
}
impl From<&Session> for TenantIdentity {
fn from(sess: &Session) -> Self {
Self {
namespace: sess.ns.clone(),
database: sess.db.clone(),
user: session_user(sess),
session_id: sess.id,
client_ip: session_client_ip(sess),
}
}
}
impl From<&Session> for NetworkBytesEventCtx {
fn from(sess: &Session) -> Self {
Self {
namespace: sess.ns.clone(),
database: sess.db.clone(),
user: session_user(sess),
}
}
}
impl From<&Session> for HttpRequestEventCtx {
fn from(sess: &Session) -> Self {
Self {
namespace: sess.ns.clone(),
database: sess.db.clone(),
user: session_user(sess),
session_id: sess.id,
client_ip: session_client_ip(sess),
}
}
}
#[cfg(test)]
mod network_ctx_from_session {
use std::sync::Arc;
use crate::dbs::Session;
use crate::iam::{Auth, Role};
use crate::observe::NetworkBytesEventCtx;
#[test]
fn root_user_populates_user() {
let sess = Session {
au: Arc::new(Auth::for_root(Role::Owner)),
..Session::default()
}
.with_ns("acme")
.with_db("prod");
let ctx = NetworkBytesEventCtx::from(&sess);
assert_eq!(ctx.namespace.as_deref(), Some("acme"));
assert_eq!(ctx.database.as_deref(), Some("prod"));
assert_eq!(ctx.user.as_deref(), Some("system_auth"));
}
#[test]
fn record_principal_collapses_to_sentinel() {
let sess = Session {
au: Arc::new(Auth::for_record("user:abc123".to_owned(), "acme", "prod", "web")),
..Session::default()
};
let ctx = NetworkBytesEventCtx::from(&sess);
assert_eq!(ctx.user.as_deref(), Some("<record>"));
assert!(
!ctx.user.as_ref().unwrap().contains("abc123"),
"record id leaked into ctx.user: {:?}",
ctx.user
);
}
#[test]
fn anonymous_session_leaves_user_none() {
let sess = Session::default();
let ctx = NetworkBytesEventCtx::from(&sess);
assert!(ctx.user.is_none());
}
#[test]
fn ns_db_carry_through_verbatim() {
let sess = Session::default().with_ns("a-ns").with_db("a-db");
let ctx = NetworkBytesEventCtx::from(&sess);
assert_eq!(ctx.namespace.as_deref(), Some("a-ns"));
assert_eq!(ctx.database.as_deref(), Some("a-db"));
}
}
#[cfg(test)]
mod tenant_identity_projections {
use std::sync::Arc;
use crate::dbs::Session;
use crate::iam::{Auth, Role};
use crate::observe::TenantIdentity;
#[test]
fn rpc_ctx_collapses_record_principal() {
let sess = Session {
au: Arc::new(Auth::for_record("user:abc123".to_owned(), "acme", "prod", "web")),
..Session::default()
};
let identity = TenantIdentity::from(&sess);
let rpc = identity.to_rpc_ctx();
assert_eq!(rpc.user.as_deref(), Some("<record>"));
assert!(
!rpc.user.as_ref().unwrap().contains("abc123"),
"record id leaked into rpc ctx.user: {:?}",
rpc.user
);
}
#[test]
fn auth_ctx_collapses_record_principal() {
let sess = Session {
au: Arc::new(Auth::for_record("user:abc123".to_owned(), "acme", "prod", "web")),
..Session::default()
};
let identity = TenantIdentity::from(&sess);
let auth = identity.to_auth_ctx();
assert_eq!(auth.user.as_deref(), Some("<record>"));
assert!(
!auth.user.as_ref().unwrap().contains("abc123"),
"record id leaked into auth ctx.user: {:?}",
auth.user
);
}
#[test]
fn projections_drop_user_for_anonymous() {
let sess = Session::default();
let identity = TenantIdentity::from(&sess);
let rpc = identity.to_rpc_ctx();
let auth = identity.to_auth_ctx();
assert!(rpc.user.is_none(), "anonymous session must not surface a user label");
assert!(auth.user.is_none(), "anonymous session must not surface a user label");
}
#[test]
fn projections_carry_root_user_id() {
let sess = Session {
au: Arc::new(Auth::for_root(Role::Owner)),
..Session::default()
}
.with_ns("acme")
.with_db("prod");
let identity = TenantIdentity::from(&sess);
let rpc = identity.to_rpc_ctx();
let auth = identity.to_auth_ctx();
assert_eq!(rpc.namespace.as_deref(), Some("acme"));
assert_eq!(rpc.database.as_deref(), Some("prod"));
assert_eq!(rpc.user.as_deref(), Some("system_auth"));
assert_eq!(auth.user.as_deref(), Some("system_auth"));
}
}