lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

authorization.rs (4450B)


      1 //! Current signing authorization, separate from historical plan integrity.
      2 
      3 use radroots_event::contract::{EventAuthoringPolicy, EventStability, event_contract};
      4 use radroots_event_codec::authoring::AuthoredEventPlan;
      5 
      6 use crate::{Actor, actor::ActorSource};
      7 
      8 /// Current policy decision for one historically valid authored plan.
      9 #[non_exhaustive]
     10 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     11 pub enum CurrentAuthoringDecision {
     12     Allowed,
     13     AllowedDeprecated { warning_code: &'static str },
     14     Blocked { code: &'static str },
     15     Revoked { code: &'static str },
     16 }
     17 
     18 /// Host or registry authority for current signing policy.
     19 pub trait CurrentAuthoringAuthority: Send + Sync {
     20     fn evaluate(&self, plan: &AuthoredEventPlan) -> CurrentAuthoringDecision;
     21 }
     22 
     23 /// Current immutable registry policy.
     24 #[derive(Clone, Copy, Debug, Default)]
     25 pub struct CurrentRegistryAuthority;
     26 
     27 impl CurrentAuthoringAuthority for CurrentRegistryAuthority {
     28     fn evaluate(&self, plan: &AuthoredEventPlan) -> CurrentAuthoringDecision {
     29         let Some(contract) = event_contract(plan.body().contract().contract_id().as_str()) else {
     30             return CurrentAuthoringDecision::Blocked {
     31                 code: "contract_not_current",
     32             };
     33         };
     34         if contract.authoring_policy() == EventAuthoringPolicy::ReadOnly {
     35             return CurrentAuthoringDecision::Revoked {
     36                 code: "contract_read_only",
     37             };
     38         }
     39         match contract.stability {
     40             EventStability::Stable => CurrentAuthoringDecision::Allowed,
     41             EventStability::Experimental => CurrentAuthoringDecision::AllowedDeprecated {
     42                 warning_code: "contract_experimental",
     43             },
     44         }
     45     }
     46 }
     47 
     48 /// Whether a request explicitly accepts a currently deprecated contract.
     49 #[non_exhaustive]
     50 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     51 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
     52 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
     53 pub enum DeprecatedPlanPolicy {
     54     #[default]
     55     Deny,
     56     Allow,
     57 }
     58 
     59 /// Host-owned policy limiting which validated actor provenance may sign.
     60 #[non_exhaustive]
     61 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     62 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
     63 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
     64 pub enum ManagedSigningPolicy {
     65     #[default]
     66     AnyValidatedSource,
     67     AccountBackedOnly,
     68     LocalAccountOnly,
     69 }
     70 
     71 impl ManagedSigningPolicy {
     72     #[must_use]
     73     pub const fn permits(self, actor: &Actor) -> bool {
     74         match self {
     75             Self::AnyValidatedSource => true,
     76             Self::AccountBackedOnly => actor.source().account_id().is_some(),
     77             Self::LocalAccountOnly => matches!(actor.source(), ActorSource::LocalAccount(_)),
     78         }
     79     }
     80 }
     81 
     82 #[cfg(test)]
     83 mod tests {
     84     use radroots_event::contract::AuthorRole;
     85     use radroots_identity::{AccountId, PublicKey};
     86 
     87     use super::*;
     88 
     89     const KEY: &str = "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df";
     90 
     91     #[test]
     92     fn managed_signing_policy_covers_every_provenance_class() {
     93         let public_key = PublicKey::from_hex(KEY).expect("public key");
     94         let account_id = AccountId::from_hex(KEY).expect("account ID");
     95         let explicit = Actor::new(
     96             public_key,
     97             ActorSource::ExplicitPublicKey,
     98             [AuthorRole::Any],
     99         )
    100         .expect("explicit actor");
    101         let local = Actor::new(
    102             public_key,
    103             ActorSource::LocalAccount(account_id),
    104             [AuthorRole::Any],
    105         )
    106         .expect("local actor");
    107         let remote = Actor::new(
    108             public_key,
    109             ActorSource::RemoteSigner(account_id),
    110             [AuthorRole::Any],
    111         )
    112         .expect("remote actor");
    113 
    114         for actor in [&explicit, &local, &remote] {
    115             assert!(ManagedSigningPolicy::AnyValidatedSource.permits(actor));
    116         }
    117         assert!(!ManagedSigningPolicy::AccountBackedOnly.permits(&explicit));
    118         assert!(ManagedSigningPolicy::AccountBackedOnly.permits(&local));
    119         assert!(ManagedSigningPolicy::AccountBackedOnly.permits(&remote));
    120         assert!(!ManagedSigningPolicy::LocalAccountOnly.permits(&explicit));
    121         assert!(ManagedSigningPolicy::LocalAccountOnly.permits(&local));
    122         assert!(!ManagedSigningPolicy::LocalAccountOnly.permits(&remote));
    123     }
    124 }