backbone_core/actor_roles.rs
1//! The roles of whoever is writing, for rules that ask (`$actor.has_role('admin')`).
2//!
3//! Roles are not on the request: the composing service knows where they live (serpa reads
4//! its authorization tables) and installs an [`ActorRoles`] once at startup. A compiled rule
5//! asks it only when the rule runs. Without one installed, a role rule reads null and holds,
6//! so a deployment that never wires roles is reported once and refused nothing.
7
8use std::sync::{Arc, OnceLock, RwLock};
9
10use async_trait::async_trait;
11
12/// Where the writer's roles come from.
13#[async_trait]
14pub trait ActorRoles: Send + Sync {
15 /// Whether `actor` holds `role`; `None` when it cannot be told (an unknown actor, a
16 /// failed lookup). Role names compare case-insensitively.
17 async fn holds(&self, actor: &str, role: &str) -> Option<bool>;
18}
19
20fn slot() -> &'static RwLock<Option<Arc<dyn ActorRoles>>> {
21 static SLOT: OnceLock<RwLock<Option<Arc<dyn ActorRoles>>>> = OnceLock::new();
22 SLOT.get_or_init(|| RwLock::new(None))
23}
24
25/// Install the roles source the role rules ask. A later install replaces it.
26pub fn install(roles: Arc<dyn ActorRoles>) {
27 *slot().write().unwrap_or_else(|e| e.into_inner()) = Some(roles);
28}
29
30pub(crate) fn installed() -> Option<Arc<dyn ActorRoles>> {
31 slot().read().unwrap_or_else(|e| e.into_inner()).clone()
32}