cratestack_core/context/
principal.rs1use std::collections::BTreeMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::CoolError;
11use crate::value::Value;
12
13use super::identity::CoolAuthIdentity;
14
15#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
16pub struct PrincipalFacet {
17 pub fields: BTreeMap<String, Value>,
18}
19
20#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
21pub struct PrincipalContext {
22 pub actor: Option<PrincipalFacet>,
23 pub session: Option<PrincipalFacet>,
24 pub tenant: Option<PrincipalFacet>,
25 pub claims: BTreeMap<String, Value>,
26}
27
28impl PrincipalContext {
29 pub fn from_principal<P: Serialize>(principal: P) -> Result<Self, CoolError> {
30 let auth = CoolAuthIdentity::from_principal(principal)?;
31 Ok(Self::from_auth_identity(&auth))
32 }
33
34 pub fn from_claims(claims: BTreeMap<String, Value>) -> Self {
35 Self {
36 actor: None,
37 session: None,
38 tenant: None,
39 claims,
40 }
41 }
42
43 pub fn from_auth_identity(auth: &CoolAuthIdentity) -> Self {
44 let mut claims = auth.fields.clone();
45 let actor = take_principal_facet(&mut claims, "actor");
46 let session = take_principal_facet(&mut claims, "session");
47 let tenant = take_principal_facet(&mut claims, "tenant");
48 Self {
49 actor,
50 session,
51 tenant,
52 claims,
53 }
54 }
55
56 pub fn field(&self, name: &str) -> Option<&Value> {
57 if let Some(value) = self
58 .claims
59 .get(name)
60 .or_else(|| lookup_value_path_in_map(&self.claims, name))
61 {
62 return Some(value);
63 }
64
65 let (root, rest) = name.split_once('.')?;
66 match root {
67 "actor" => lookup_principal_facet_path(self.actor.as_ref(), rest),
68 "session" => lookup_principal_facet_path(self.session.as_ref(), rest),
69 "tenant" => lookup_principal_facet_path(self.tenant.as_ref(), rest),
70 _ => None,
71 }
72 }
73
74 pub fn as_auth_identity(&self) -> CoolAuthIdentity {
75 CoolAuthIdentity {
76 fields: self.legacy_fields(),
77 }
78 }
79
80 pub fn legacy_fields(&self) -> BTreeMap<String, Value> {
81 let mut fields = self.claims.clone();
82 if let Some(actor) = &self.actor {
83 fields.insert("actor".to_owned(), Value::Map(actor.fields.clone()));
84 }
85 if let Some(session) = &self.session {
86 fields.insert("session".to_owned(), Value::Map(session.fields.clone()));
87 }
88 if let Some(tenant) = &self.tenant {
89 fields.insert("tenant".to_owned(), Value::Map(tenant.fields.clone()));
90 }
91 fields
92 }
93}
94
95pub(super) fn lookup_value_path_in_map<'a>(
96 map: &'a BTreeMap<String, Value>,
97 path: &str,
98) -> Option<&'a Value> {
99 let mut segments = path.split('.');
100 let first = segments.next()?;
101 let mut current = map.get(first)?;
102 for segment in segments {
103 current = match current {
104 Value::Map(entries) => entries.get(segment)?,
105 _ => return None,
106 };
107 }
108 Some(current)
109}
110
111fn lookup_principal_facet_path<'a>(
112 facet: Option<&'a PrincipalFacet>,
113 path: &str,
114) -> Option<&'a Value> {
115 let facet = facet?;
116 facet
117 .fields
118 .get(path)
119 .or_else(|| lookup_value_path_in_map(&facet.fields, path))
120}
121
122fn take_principal_facet(claims: &mut BTreeMap<String, Value>, key: &str) -> Option<PrincipalFacet> {
123 match claims.remove(key) {
124 Some(Value::Map(fields)) => Some(PrincipalFacet { fields }),
125 Some(value) => {
126 claims.insert(key.to_owned(), value);
127 None
128 }
129 None => None,
130 }
131}