1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::value::{DictMap, VmValue};
8
9const SUB: &str = "sub";
10const ACT: &str = "act";
11const MAY_ACT: &str = "may_act";
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub struct ActorChain {
23 origin: String,
24 actors: Vec<String>,
25 may_act: Option<String>,
26}
27
28pub type Principal = ActorChain;
30
31impl ActorChain {
32 pub fn new(origin: impl Into<String>) -> Self {
34 Self {
35 origin: origin.into(),
36 actors: Vec::new(),
37 may_act: None,
38 }
39 }
40
41 pub fn from_parts<I, S>(origin: impl Into<String>, actors: I) -> Self
44 where
45 I: IntoIterator<Item = S>,
46 S: Into<String>,
47 {
48 Self {
49 origin: origin.into(),
50 actors: actors.into_iter().map(Into::into).collect(),
51 may_act: None,
52 }
53 }
54
55 pub fn push(&mut self, actor: impl Into<String>) {
57 self.actors.insert(0, actor.into());
58 }
59
60 pub fn pushed(mut self, actor: impl Into<String>) -> Self {
62 self.push(actor);
63 self
64 }
65
66 pub fn current(&self) -> &str {
71 self.actors
72 .first()
73 .map(String::as_str)
74 .unwrap_or(self.origin.as_str())
75 }
76
77 pub fn origin(&self) -> &str {
79 &self.origin
80 }
81
82 pub fn actors(&self) -> impl Iterator<Item = &str> {
84 self.actors.iter().map(String::as_str)
85 }
86
87 pub fn iter(&self) -> ActorChainIter<'_> {
89 ActorChainIter {
90 actors: self.actors.iter(),
91 origin: Some(self.origin.as_str()),
92 }
93 }
94
95 pub fn is_delegated(&self) -> bool {
96 !self.actors.is_empty()
97 }
98
99 pub fn may_act(&self) -> Option<&str> {
101 self.may_act.as_deref()
102 }
103
104 pub fn set_may_act(&mut self, may_act: impl Into<String>) {
105 self.may_act = Some(may_act.into());
106 }
107
108 pub fn with_may_act(mut self, may_act: impl Into<String>) -> Self {
109 self.set_may_act(may_act);
110 self
111 }
112
113 pub fn clear_may_act(&mut self) {
114 self.may_act = None;
115 }
116
117 pub fn to_json_value(&self) -> serde_json::Value {
118 let mut root = serde_json::Map::new();
119 root.insert(
120 SUB.to_string(),
121 serde_json::Value::String(self.origin.clone()),
122 );
123 if let Some(act) = actor_nodes_to_json(&self.actors) {
124 root.insert(ACT.to_string(), act);
125 }
126 if let Some(may_act) = &self.may_act {
127 root.insert(MAY_ACT.to_string(), principal_claim_json(may_act));
128 }
129 serde_json::Value::Object(root)
130 }
131
132 pub fn from_json_value(value: &serde_json::Value) -> Result<Self, ActorChainError> {
133 let root = expect_json_object(value, "$")?;
134 let origin = json_sub(root, "$")?;
135 let actors = json_actor_subjects(root.get(ACT), "$.act")?;
136 let may_act = root
137 .get(MAY_ACT)
138 .map(|value| {
139 let may_act = expect_json_object(value, "$.may_act")?;
140 json_sub(may_act, "$.may_act")
141 })
142 .transpose()?;
143 Ok(Self {
144 origin,
145 actors,
146 may_act,
147 })
148 }
149
150 pub fn to_vm_value(&self) -> VmValue {
151 crate::schema::json_to_vm_value(&self.to_json_value())
152 }
153
154 pub fn from_vm_value(value: &VmValue) -> Result<Self, ActorChainError> {
155 let root = expect_vm_dict(value, "$")?;
156 let origin = vm_sub(root, "$")?;
157 let actors = vm_actor_subjects(root.get(ACT), "$.act")?;
158 let may_act = root
159 .get(MAY_ACT)
160 .map(|value| {
161 let may_act = expect_vm_dict(value, "$.may_act")?;
162 vm_sub(may_act, "$.may_act")
163 })
164 .transpose()?;
165 Ok(Self {
166 origin,
167 actors,
168 may_act,
169 })
170 }
171}
172
173impl Serialize for ActorChain {
174 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175 where
176 S: serde::Serializer,
177 {
178 self.to_json_value().serialize(serializer)
179 }
180}
181
182impl<'de> Deserialize<'de> for ActorChain {
183 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184 where
185 D: serde::Deserializer<'de>,
186 {
187 let value = serde_json::Value::deserialize(deserializer)?;
188 Self::from_json_value(&value).map_err(serde::de::Error::custom)
189 }
190}
191
192impl TryFrom<&serde_json::Value> for ActorChain {
193 type Error = ActorChainError;
194
195 fn try_from(value: &serde_json::Value) -> Result<Self, Self::Error> {
196 Self::from_json_value(value)
197 }
198}
199
200impl From<&ActorChain> for VmValue {
201 fn from(chain: &ActorChain) -> Self {
202 chain.to_vm_value()
203 }
204}
205
206impl From<ActorChain> for VmValue {
207 fn from(chain: ActorChain) -> Self {
208 chain.to_vm_value()
209 }
210}
211
212impl TryFrom<&VmValue> for ActorChain {
213 type Error = ActorChainError;
214
215 fn try_from(value: &VmValue) -> Result<Self, Self::Error> {
216 Self::from_vm_value(value)
217 }
218}
219
220pub struct ActorChainIter<'a> {
221 actors: std::slice::Iter<'a, String>,
222 origin: Option<&'a str>,
223}
224
225impl<'a> Iterator for ActorChainIter<'a> {
226 type Item = &'a str;
227
228 fn next(&mut self) -> Option<Self::Item> {
229 self.actors
230 .next()
231 .map(String::as_str)
232 .or_else(|| self.origin.take())
233 }
234}
235
236impl<'a> IntoIterator for &'a ActorChain {
237 type Item = &'a str;
238 type IntoIter = ActorChainIter<'a>;
239
240 fn into_iter(self) -> Self::IntoIter {
241 self.iter()
242 }
243}
244
245#[derive(Clone, Debug, PartialEq, Eq)]
246pub struct ActorChainError {
247 message: String,
248}
249
250impl ActorChainError {
251 fn new(message: impl Into<String>) -> Self {
252 Self {
253 message: message.into(),
254 }
255 }
256
257 pub fn message(&self) -> &str {
258 &self.message
259 }
260}
261
262impl fmt::Display for ActorChainError {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 self.message.fmt(f)
265 }
266}
267
268impl std::error::Error for ActorChainError {}
269
270fn actor_nodes_to_json(actors: &[String]) -> Option<serde_json::Value> {
271 let mut next = None;
272 for actor in actors.iter().rev() {
273 let mut node = serde_json::Map::new();
274 node.insert(SUB.to_string(), serde_json::Value::String(actor.clone()));
275 if let Some(act) = next {
276 node.insert(ACT.to_string(), act);
277 }
278 next = Some(serde_json::Value::Object(node));
279 }
280 next
281}
282
283fn principal_claim_json(subject: &str) -> serde_json::Value {
284 let mut claim = serde_json::Map::new();
285 claim.insert(
286 SUB.to_string(),
287 serde_json::Value::String(subject.to_string()),
288 );
289 serde_json::Value::Object(claim)
290}
291
292fn expect_json_object<'a>(
293 value: &'a serde_json::Value,
294 path: &str,
295) -> Result<&'a serde_json::Map<String, serde_json::Value>, ActorChainError> {
296 value
297 .as_object()
298 .ok_or_else(|| ActorChainError::new(format!("{path} must be an object")))
299}
300
301fn json_sub(
302 object: &serde_json::Map<String, serde_json::Value>,
303 path: &str,
304) -> Result<String, ActorChainError> {
305 object
306 .get(SUB)
307 .and_then(serde_json::Value::as_str)
308 .map(ToString::to_string)
309 .ok_or_else(|| ActorChainError::new(format!("{path}.sub must be a string")))
310}
311
312fn json_actor_subjects(
313 first: Option<&serde_json::Value>,
314 first_path: &str,
315) -> Result<Vec<String>, ActorChainError> {
316 let mut actors = Vec::new();
317 let mut current = first;
318 let mut path = first_path.to_string();
319 while let Some(value) = current {
320 let object = expect_json_object(value, &path)?;
321 actors.push(json_sub(object, &path)?);
322 current = object.get(ACT);
323 path.push_str(".act");
324 }
325 Ok(actors)
326}
327
328fn expect_vm_dict<'a>(value: &'a VmValue, path: &str) -> Result<&'a DictMap, ActorChainError> {
329 value
330 .as_dict()
331 .ok_or_else(|| ActorChainError::new(format!("{path} must be a dict")))
332}
333
334fn vm_sub(object: &DictMap, path: &str) -> Result<String, ActorChainError> {
335 match object.get(SUB) {
336 Some(VmValue::String(subject)) => Ok(subject.to_string()),
337 _ => Err(ActorChainError::new(format!("{path}.sub must be a string"))),
338 }
339}
340
341fn vm_actor_subjects(
342 first: Option<&VmValue>,
343 first_path: &str,
344) -> Result<Vec<String>, ActorChainError> {
345 let mut actors = Vec::new();
346 let mut current = first;
347 let mut path = first_path.to_string();
348 while let Some(value) = current {
349 let object = expect_vm_dict(value, &path)?;
350 actors.push(vm_sub(object, &path)?);
351 current = object.get(ACT);
352 path.push_str(".act");
353 }
354 Ok(actors)
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use proptest::prelude::*;
361 use serde_json::json;
362
363 fn principal() -> impl Strategy<Value = String> {
364 proptest::string::string_regex("[a-z][a-z0-9_]{0,24}").unwrap()
365 }
366
367 fn actor_subjects_from_json(value: &serde_json::Value) -> Vec<String> {
368 let mut subjects = Vec::new();
369 let mut current = value.get(ACT);
370 while let Some(node) = current {
371 subjects.push(node[SUB].as_str().expect("act.sub").to_string());
372 current = node.get(ACT);
373 }
374 subjects
375 }
376
377 #[test]
378 fn push_makes_new_actor_current_and_serializes_rfc_shape() {
379 let chain = ActorChain::new("user")
380 .pushed("service77")
381 .pushed("service16");
382
383 assert_eq!(chain.origin(), "user");
384 assert_eq!(chain.current(), "service16");
385 assert_eq!(
386 chain.iter().collect::<Vec<_>>(),
387 vec!["service16", "service77", "user"]
388 );
389 assert_eq!(
390 serde_json::to_value(&chain).unwrap(),
391 json!({
392 "sub": "user",
393 "act": {
394 "sub": "service16",
395 "act": {
396 "sub": "service77"
397 }
398 }
399 })
400 );
401 }
402
403 #[test]
404 fn may_act_serializes_as_authorized_actor_claim() {
405 let chain = ActorChain::new("user").with_may_act("admin");
406
407 assert_eq!(
408 serde_json::to_value(&chain).unwrap(),
409 json!({
410 "sub": "user",
411 "may_act": {
412 "sub": "admin"
413 }
414 })
415 );
416 }
417
418 #[test]
419 fn vm_value_conversion_round_trips_plain_harn_dicts() {
420 let value = crate::schema::json_to_vm_value(&json!({
421 "sub": "user",
422 "act": {
423 "sub": "service16",
424 "act": {
425 "sub": "service77"
426 }
427 },
428 "may_act": {
429 "sub": "admin"
430 }
431 }));
432
433 let chain = ActorChain::try_from(&value).unwrap();
434 assert_eq!(chain.origin(), "user");
435 assert_eq!(chain.current(), "service16");
436 assert_eq!(
437 chain.actors().collect::<Vec<_>>(),
438 vec!["service16", "service77"]
439 );
440 assert_eq!(chain.may_act(), Some("admin"));
441
442 let encoded = chain.to_vm_value();
443 assert_eq!(ActorChain::try_from(&encoded).unwrap(), chain);
444 }
445
446 proptest! {
447 #[test]
448 fn serde_round_trip_preserves_nesting_order(
449 origin in principal(),
450 actors in proptest::collection::vec(principal(), 0..10),
451 may_act in proptest::option::of(principal()),
452 ) {
453 let mut chain = ActorChain::from_parts(origin.clone(), actors.clone());
454 if let Some(may_act) = may_act.as_deref() {
455 chain.set_may_act(may_act);
456 }
457
458 let encoded = serde_json::to_value(&chain).unwrap();
459 prop_assert_eq!(encoded[SUB].as_str(), Some(origin.as_str()));
460 let encoded_actors = actor_subjects_from_json(&encoded);
461 prop_assert_eq!(encoded_actors.as_slice(), actors.as_slice());
462 prop_assert_eq!(
463 encoded.get(MAY_ACT).and_then(|claim| claim[SUB].as_str()),
464 may_act.as_deref()
465 );
466
467 let decoded: ActorChain = serde_json::from_value(encoded.clone()).unwrap();
468 prop_assert_eq!(&decoded, &chain);
469 prop_assert_eq!(serde_json::to_value(&decoded).unwrap(), encoded);
470 prop_assert_eq!(decoded.origin(), origin.as_str());
471 prop_assert_eq!(
472 decoded.current(),
473 actors.first().map(String::as_str).unwrap_or(origin.as_str())
474 );
475
476 let expected_iter = actors
477 .iter()
478 .map(String::as_str)
479 .chain(std::iter::once(origin.as_str()))
480 .collect::<Vec<_>>();
481 prop_assert_eq!(decoded.iter().collect::<Vec<_>>(), expected_iter);
482 }
483 }
484}