khive_runtime/
mailbox_view.rs1use std::sync::Arc;
2
3use khive_gate::{check_with_mailbox_policy, mailbox_read_owner};
4use serde_json::Value;
5use sha2::{Digest, Sha256};
6
7use crate::{
8 engine_config::ActorConfig, ActorRef, Gate, GateDecision, GateError, GateRef, GateRequest,
9 KhiveRuntime, MailboxPolicyError, MailboxReadGate, NamespaceToken, RuntimeError, RuntimeResult,
10};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct MailboxView {
15 pub actor_id: String,
16 pub delegated: bool,
17}
18
19pub(crate) fn validate_mailbox_request(req: &GateRequest) -> RuntimeResult<()> {
22 mailbox_read_owner(req)
23 .map(|_| ())
24 .map_err(|error| RuntimeError::InvalidInput(error.to_string()))
25}
26
27impl KhiveRuntime {
28 pub fn authorize_mailbox_view(
34 &self,
35 token: &NamespaceToken,
36 verb: &str,
37 selector: Option<&str>,
38 args: &Value,
39 ) -> RuntimeResult<MailboxView> {
40 let selector_field = match verb {
41 "comm.inbox" | "comm.thread" => "mailbox_actor",
42 "comm.probe" => "actor",
43 _ => {
44 return Err(RuntimeError::InvalidInput(
45 "mailbox views are supported only by comm.inbox, comm.thread and comm.probe"
46 .into(),
47 ));
48 }
49 };
50 let req = GateRequest::new(
51 token.actor().clone(),
52 token.gate_namespace().clone(),
53 verb,
54 args.clone(),
55 );
56 validate_mailbox_request(&req)?;
57 if args.get(selector_field).and_then(Value::as_str) != selector {
58 return Err(RuntimeError::InvalidInput(format!(
59 "mailbox selector must match the original {selector_field} argument"
60 )));
61 }
62 match check_with_mailbox_policy(self.config().gate.as_ref(), &req) {
63 Ok(GateDecision::Allow { .. }) => {
64 let owner = mailbox_read_owner(&req)
65 .map_err(|error| RuntimeError::InvalidInput(error.to_string()))?;
66 Ok(MailboxView {
67 delegated: owner.is_some(),
68 actor_id: owner.map_or_else(|| token.actor().id.clone(), |owner| owner.id),
69 })
70 }
71 Ok(GateDecision::Deny { reason }) => Err(RuntimeError::permission_denied(verb, reason)),
72 Err(error) => Err(RuntimeError::GateUnavailable {
73 verb: verb.to_string(),
74 reason: error.wire_reason().to_string(),
75 }),
76 }
77 }
78}
79
80impl ActorConfig {
81 pub(crate) fn mailbox_gate(
82 &self,
83 inner: GateRef,
84 ) -> Result<Option<MailboxReadGate>, MailboxPolicyError> {
85 if self.mailbox_readers.is_empty() {
86 return Ok(None);
87 }
88 if self.mailbox_readers.len() > 256 {
89 return Err(MailboxPolicyError::TooManyReaders);
90 }
91 if self
92 .mailbox_readers
93 .iter()
94 .any(|id| !crate::is_valid_mailbox_actor_label(id))
95 {
96 return Err(MailboxPolicyError::InvalidReader);
97 }
98 let owner = self.id.as_deref().ok_or(MailboxPolicyError::InvalidOwner)?;
101 crate::Namespace::parse(owner).map_err(|_| MailboxPolicyError::InvalidOwner)?;
102 let readers = self
103 .mailbox_readers
104 .iter()
105 .map(|id| ActorRef::new("actor", id.clone()))
106 .collect();
107 MailboxReadGate::new(inner, ActorRef::new("actor", owner), readers).map(Some)
108 }
109}
110
111pub(crate) fn configured_mailbox_gate(actor: &ActorConfig, inner: GateRef) -> GateRef {
115 match actor.mailbox_gate(inner.clone()) {
116 Ok(Some(gate)) => Arc::new(gate),
117 Ok(None) => inner,
118 Err(_) => {
119 let mut hash = Sha256::new();
120 hash.update(b"khive.invalid-mailbox-read-policy.v1\0");
121 for field in [inner.configuration_fingerprint(), actor.id.as_deref()] {
122 match field {
123 Some(value) => {
124 hash.update([1]);
125 hash.update((value.len() as u64).to_be_bytes());
126 hash.update(value.as_bytes());
127 }
128 None => hash.update([0]),
129 }
130 }
131 hash.update((actor.mailbox_readers.len() as u64).to_be_bytes());
132 for reader in &actor.mailbox_readers {
133 hash.update((reader.len() as u64).to_be_bytes());
134 hash.update(reader.as_bytes());
135 }
136 Arc::new(InvalidMailboxConfigGate {
137 fingerprint: format!("sha256:{:x}", hash.finalize()),
138 })
139 }
140 }
141}
142
143#[derive(Debug)]
144struct InvalidMailboxConfigGate {
145 fingerprint: String,
146}
147
148impl Gate for InvalidMailboxConfigGate {
149 fn check(&self, _req: &GateRequest) -> Result<GateDecision, GateError> {
150 Err(GateError::Policy(
151 "invalid mailbox reader configuration".into(),
152 ))
153 }
154
155 fn configuration_fingerprint(&self) -> Option<&str> {
156 Some(&self.fingerprint)
157 }
158}
159
160#[cfg(test)]
161mod tests;