Skip to main content

valence_core/
actor_policy.rs

1//! Optional policy for validating opaque `actor_json` at factory build time.
2//!
3//! Hosts that reconstruct [`crate::actor::Actor`] from external JSON should install an
4//! [`ActorJsonPolicy`] so untrusted clients cannot mint [`crate::actor::Actor::System`].
5
6use serde_json::Value;
7
8use crate::error::{Error, Result};
9
10/// Trust level for an actor JSON call site.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ActorTrust {
13    /// In-process / bootstrapped callers (may mint elevated actor shapes when policy allows).
14    Internal,
15    /// Externally reachable surfaces (HTTP, jobs with client-supplied JSON, etc.).
16    External,
17}
18
19/// Validates `actor_json` before a [`crate::runtime::ValenceFactory`] binds an actor.
20///
21/// # Errors
22///
23/// Implementations return [`Error::Validation`] when the actor is rejected.
24pub trait ActorJsonPolicy: Send + Sync {
25    /// Validate actor JSON for the given trust level.
26    ///
27    /// # Errors
28    ///
29    /// Returns an error when the actor must not be bound.
30    fn validate(&self, trust: ActorTrust, actor_json: &Value) -> Result<()>;
31}
32
33/// Rejects well-known System-shaped actors on [`ActorTrust::External`] paths.
34///
35/// Recognizes `{"System": ...}` object keys (case-sensitive), matching [`crate::actor::Actor`].
36#[derive(Debug, Default, Clone, Copy)]
37pub struct RejectExternalSystemActor;
38
39impl ActorJsonPolicy for RejectExternalSystemActor {
40    fn validate(&self, trust: ActorTrust, actor_json: &Value) -> Result<()> {
41        if trust == ActorTrust::External && is_system_shaped_actor(actor_json) {
42            return Err(Error::Validation(
43                "external actor_json cannot use System-shaped actor".into(),
44            ));
45        }
46        Ok(())
47    }
48}
49
50/// True when `actor_json` uses the well-known System object key.
51#[must_use]
52pub fn is_system_shaped_actor(actor_json: &Value) -> bool {
53    actor_json.get("System").is_some()
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn reject_external_system() {
62        let policy = RejectExternalSystemActor;
63        let system = serde_json::json!({"System": {"operation": "x"}});
64        assert!(policy
65            .validate(ActorTrust::External, &system)
66            .unwrap_err()
67            .to_string()
68            .contains("System"));
69        assert!(policy.validate(ActorTrust::Internal, &system).is_ok());
70    }
71
72    #[test]
73    fn allow_external_user() {
74        let policy = RejectExternalSystemActor;
75        let user = serde_json::json!({"User": {"user_id": "u1"}});
76        assert!(!is_system_shaped_actor(&user));
77        assert!(policy.validate(ActorTrust::External, &user).is_ok());
78    }
79}