use std::cell::RefCell;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Identity {
kind: Arc<str>,
id: Arc<str>,
subject: Arc<str>,
source: &'static str,
claims: Option<Arc<str>>,
}
impl Identity {
pub fn new(subject: impl AsRef<str>, source: &'static str) -> Identity {
let subject = subject.as_ref();
let (kind, id) = subject.split_once(':').unwrap_or(("user", subject));
Identity::typed(kind, id, source)
}
fn typed(kind: &str, id: &str, source: &'static str) -> Identity {
Identity { kind: Arc::from(kind), id: Arc::from(id), subject: Arc::from(format!("{kind}:{id}")), source, claims: None }
}
pub fn user(id: &str, source: &'static str) -> Option<Identity> {
crate::authz::rebac::ObjectRef::parse(&format!("user:{id}")).ok()?;
Some(Identity::typed("user", id, source))
}
pub fn with_claims(mut self, claims: impl AsRef<str>) -> Identity {
self.claims = Some(Arc::from(claims.as_ref()));
self
}
pub fn from_claims(claims: &str, claim: &str, source: &'static str) -> Option<Identity> {
Some(Identity::user(&claim_value(claims, claim)?, source)?.with_claims(claims))
}
pub fn current() -> Option<Identity> {
#[cfg(feature = "http2")]
if let Ok(Some(identity)) = TASK.try_with(|i| i.clone()) {
return Some(identity);
}
CURRENT.with(|c| c.borrow().clone())
}
pub fn subject(&self) -> &str {
&self.subject
}
pub fn subject_type(&self) -> &str {
&self.kind
}
pub fn subject_id(&self) -> &str {
&self.id
}
pub fn as_subject(&self) -> crate::authz::Subject {
crate::authz::Subject { object: crate::authz::ObjectRef { kind: self.kind.to_string(), id: self.id.to_string() }, relation: None }
}
pub fn source(&self) -> &'static str {
self.source
}
pub fn claims(&self) -> Option<&str> {
self.claims.as_deref()
}
pub fn claim(&self, name: &str) -> Option<String> {
claim_value(self.claims.as_deref()?, name)
}
}
pub(crate) fn claim_value(claims: &str, name: &str) -> Option<String> {
let members = crate::mcp::json_rpc::object_members(claims)?;
let (_, raw) = members.into_iter().find(|(key, _)| key == name)?;
raw.trim().starts_with('"').then(|| crate::mcp::json_rpc::extract_str(&format!("{{\"v\":{raw}}}"), "v")).flatten()
}
thread_local! {
static CURRENT: RefCell<Option<Identity>> = const { RefCell::new(None) };
}
#[cfg(feature = "http2")]
tokio::task_local! {
static TASK: Option<Identity>;
}
pub fn with_identity<R>(identity: Identity, f: impl FnOnce() -> R) -> R {
let _guard = enter(identity);
f()
}
pub(crate) fn enter(identity: Identity) -> IdentityGuard {
IdentityGuard(CURRENT.with(|c| c.borrow_mut().replace(identity)))
}
pub(crate) struct IdentityGuard(Option<Identity>);
impl Drop for IdentityGuard {
fn drop(&mut self) {
let previous = self.0.take();
CURRENT.with(|c| *c.borrow_mut() = previous);
}
}
#[cfg(feature = "http2")]
pub async fn scope_identity<F: std::future::Future>(identity: Identity, future: F) -> F::Output {
TASK.scope(Some(identity), future).await
}