noosphere_core/authority/
authorization.rs

1use std::{convert::TryFrom, fmt::Display, str::FromStr};
2
3use anyhow::{anyhow, Result};
4use cid::Cid;
5use libipld_core::{ipld::Ipld, raw::RawCodec};
6use noosphere_storage::block_encode;
7use ucan::{chain::ProofChain, crypto::did::DidParser, store::UcanJwtStore, Ucan};
8
9#[cfg(doc)]
10use ucan::ipld::UcanIpld;
11
12#[cfg(doc)]
13use crate::data::Jwt;
14
15use super::SUPPORTED_KEYS;
16
17/// An [Authorization] is a wrapper around something that can be resolved into
18/// a [Ucan]. Typically this is a [Cid], but it may also be something like a
19/// [Ucan] itself, or a [UcanIpld], or a [Jwt]. We don't want to deal with each
20/// of these heterogenous types separately, so we move them around as an
21/// [Authorization] instead.
22/// TODO(ucan-wg/rs-ucan#32): Maybe swap this out is we get a substantially
23/// similar construct to land in rs-ucan
24#[derive(Clone, Debug)]
25pub enum Authorization {
26    /// A fully instantiated [Ucan]
27    Ucan(Ucan),
28    /// A [Cid] that refers to a [Ucan] that may be looked up in storage at the
29    /// point of invocation
30    Cid(Cid),
31}
32
33impl Authorization {
34    /// Attempt to resolve the [Authorization] as a fully deserialized [Ucan]
35    /// (if it is not one already).
36    pub async fn as_ucan<S: UcanJwtStore>(&self, store: &S) -> Result<Ucan> {
37        match self {
38            Authorization::Ucan(ucan) => Ok(ucan.clone()),
39            Authorization::Cid(cid) => Ucan::from_str(&store.require_token(cid).await?),
40        }
41    }
42
43    /// Attempt to resolve the [Authorization] as a [ProofChain] (via its associated [Ucan])
44    pub async fn as_proof_chain<S: UcanJwtStore>(&self, store: &S) -> Result<ProofChain> {
45        let mut did_parser = DidParser::new(SUPPORTED_KEYS);
46        Ok(match self {
47            Authorization::Ucan(ucan) => {
48                ProofChain::from_ucan(ucan.clone(), None, &mut did_parser, store).await?
49            }
50            Authorization::Cid(cid) => {
51                ProofChain::from_cid(cid, None, &mut did_parser, store).await?
52            }
53        })
54    }
55}
56
57impl FromStr for Authorization {
58    type Err = anyhow::Error;
59
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        let mut parts = s.trim().split(':');
62        Ok(match parts.next() {
63            Some("jwt") => Authorization::Ucan(Ucan::from_str(
64                parts.next().ok_or_else(|| anyhow!("Missing token"))?,
65            )?),
66            Some("cid") => Authorization::Cid(Cid::try_from(
67                parts.next().ok_or_else(|| anyhow!("Missing CID string"))?,
68            )?),
69            Some(any_other) => Authorization::Cid(Cid::from_str(any_other)?),
70            None => return Err(anyhow!("Authorization had empty value")),
71        })
72    }
73}
74
75impl From<Cid> for Authorization {
76    fn from(cid: Cid) -> Self {
77        Authorization::Cid(cid)
78    }
79}
80
81impl TryFrom<Authorization> for Cid {
82    type Error = anyhow::Error;
83
84    fn try_from(value: Authorization) -> Result<Self, Self::Error> {
85        Cid::try_from(&value)
86    }
87}
88
89impl TryFrom<&Authorization> for Cid {
90    type Error = anyhow::Error;
91
92    fn try_from(value: &Authorization) -> Result<Self, Self::Error> {
93        Ok(match value {
94            Authorization::Ucan(ucan) => {
95                let jwt = ucan.encode()?;
96                let (cid, _) = block_encode::<RawCodec, _>(&Ipld::Bytes(jwt.as_bytes().to_vec()))?;
97                cid
98            }
99            Authorization::Cid(cid) => *cid,
100        })
101    }
102}
103
104impl Display for Authorization {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        let cid = Cid::try_from(self).map_err(|_| std::fmt::Error)?;
107        cid.fmt(f)
108    }
109}