spacedb_access/chain.rs
1//! Delegation chains — bounded re-delegation of a capability.
2//!
3//! A bearer holding a delegable capability (`delegation_depth > 0`) can issue a
4//! **sub-grant** to another bearer, narrower than the one it holds. The presented
5//! credential is then a [`CapabilityChain`]: `[root, sub₁, sub₂, …]` where each
6//! link is signed by the previous link's bearer (the delegator), and
7//! [`authorize_chain`](crate::authorize_chain) enforces that every step narrows
8//! (scope ⊆, ops ⊆, expiry ≤, depth −1) and that no link is revoked.
9//!
10//! Accountability propagates down the chain: the final bearer's authority traces
11//! back, link by link, to the owner who signed the root.
12
13use serde::{Deserialize, Serialize};
14
15use crate::capability::{Capability, SignedCapability};
16use crate::error::{AccessError, AccessResult};
17use crate::identity::{Did, Identity};
18
19/// A root capability plus a sequence of sub-grants, each signed by the previous
20/// bearer.
21#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22pub struct CapabilityChain {
23 links: Vec<SignedCapability>,
24}
25
26impl CapabilityChain {
27 /// A chain of just the root grant (no delegation).
28 pub fn single(root: SignedCapability) -> Self {
29 Self { links: vec![root] }
30 }
31
32 /// The links, `[root, …, leaf]`.
33 pub fn links(&self) -> &[SignedCapability] {
34 &self.links
35 }
36
37 pub fn len(&self) -> usize {
38 self.links.len()
39 }
40
41 pub fn is_empty(&self) -> bool {
42 self.links.is_empty()
43 }
44
45 /// The bearer the chain ultimately authorizes (the leaf bearer).
46 pub fn bearer(&self) -> Option<&Did> {
47 self.links.last().map(|l| &l.capability.bearer)
48 }
49
50 /// Serialize the chain (postcard) for transmission.
51 pub fn encode(&self) -> AccessResult<Vec<u8>> {
52 postcard::to_allocvec(self).map_err(|e| AccessError::Canonical(e.to_string()))
53 }
54
55 /// Deserialize a chain (postcard).
56 pub fn decode(bytes: &[u8]) -> AccessResult<Self> {
57 postcard::from_bytes(bytes).map_err(|e| AccessError::Canonical(e.to_string()))
58 }
59}
60
61impl From<SignedCapability> for CapabilityChain {
62 fn from(root: SignedCapability) -> Self {
63 Self::single(root)
64 }
65}
66
67/// Extend `parent` by delegating `sub`, signed by `delegator`. The narrowing and
68/// depth constraints are enforced at authorization time
69/// ([`authorize_chain`](crate::authorize_chain)); this just signs and appends, so
70/// callers should build `sub` with `issuer = delegator.did()` and a scope/ops/
71/// expiry within the parent.
72pub fn delegate(
73 parent: &CapabilityChain,
74 sub: Capability,
75 delegator: &Identity,
76) -> AccessResult<CapabilityChain> {
77 let signed = SignedCapability::sign(sub, delegator)?;
78 let mut links = parent.links.clone();
79 links.push(signed);
80 Ok(CapabilityChain { links })
81}