rust_web_server/authz/
identity.rs1use std::cell::RefCell;
2use std::sync::Arc;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Identity {
8 kind: Arc<str>,
9 id: Arc<str>,
10 subject: Arc<str>,
11 source: &'static str,
12 claims: Option<Arc<str>>,
13}
14
15impl Identity {
16 pub fn new(subject: impl AsRef<str>, source: &'static str) -> Identity {
21 let subject = subject.as_ref();
22 let (kind, id) = subject.split_once(':').unwrap_or(("user", subject));
23 Identity::typed(kind, id, source)
24 }
25
26 fn typed(kind: &str, id: &str, source: &'static str) -> Identity {
27 Identity { kind: Arc::from(kind), id: Arc::from(id), subject: Arc::from(format!("{kind}:{id}")), source, claims: None }
28 }
29
30 pub fn user(id: &str, source: &'static str) -> Option<Identity> {
36 crate::authz::rebac::ObjectRef::parse(&format!("user:{id}")).ok()?;
37 Some(Identity::typed("user", id, source))
38 }
39
40 pub fn with_claims(mut self, claims: impl AsRef<str>) -> Identity {
42 self.claims = Some(Arc::from(claims.as_ref()));
43 self
44 }
45
46 pub fn from_claims(claims: &str, claim: &str, source: &'static str) -> Option<Identity> {
49 Some(Identity::user(&claim_value(claims, claim)?, source)?.with_claims(claims))
50 }
51
52 pub fn current() -> Option<Identity> {
54 #[cfg(feature = "http2")]
55 if let Ok(Some(identity)) = TASK.try_with(|i| i.clone()) {
56 return Some(identity);
57 }
58 CURRENT.with(|c| c.borrow().clone())
59 }
60
61 pub fn subject(&self) -> &str {
63 &self.subject
64 }
65
66 pub fn subject_type(&self) -> &str {
68 &self.kind
69 }
70
71 pub fn subject_id(&self) -> &str {
73 &self.id
74 }
75
76 pub fn as_subject(&self) -> crate::authz::Subject {
78 crate::authz::Subject { object: crate::authz::ObjectRef { kind: self.kind.to_string(), id: self.id.to_string() }, relation: None }
79 }
80
81 pub fn source(&self) -> &'static str {
83 self.source
84 }
85
86 pub fn claims(&self) -> Option<&str> {
88 self.claims.as_deref()
89 }
90
91 pub fn claim(&self, name: &str) -> Option<String> {
93 claim_value(self.claims.as_deref()?, name)
94 }
95}
96
97pub(crate) fn claim_value(claims: &str, name: &str) -> Option<String> {
99 let members = crate::mcp::json_rpc::object_members(claims)?;
100 let (_, raw) = members.into_iter().find(|(key, _)| key == name)?;
101 raw.trim().starts_with('"').then(|| crate::mcp::json_rpc::extract_str(&format!("{{\"v\":{raw}}}"), "v")).flatten()
102}
103
104thread_local! {
105 static CURRENT: RefCell<Option<Identity>> = const { RefCell::new(None) };
106}
107
108#[cfg(feature = "http2")]
109tokio::task_local! {
110 static TASK: Option<Identity>;
111}
112
113pub fn with_identity<R>(identity: Identity, f: impl FnOnce() -> R) -> R {
117 let _guard = enter(identity);
118 f()
119}
120
121pub(crate) fn enter(identity: Identity) -> IdentityGuard {
123 IdentityGuard(CURRENT.with(|c| c.borrow_mut().replace(identity)))
124}
125
126pub(crate) struct IdentityGuard(Option<Identity>);
128
129impl Drop for IdentityGuard {
130 fn drop(&mut self) {
131 let previous = self.0.take();
132 CURRENT.with(|c| *c.borrow_mut() = previous);
133 }
134}
135
136#[cfg(feature = "http2")]
138pub async fn scope_identity<F: std::future::Future>(identity: Identity, future: F) -> F::Output {
139 TASK.scope(Some(identity), future).await
140}