From 3c19e8a3c42853ea23423ad0ccad4145480f7bcc Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 24 Oct 2022 14:35:45 -0400 Subject: [PATCH] proofchain w/ builder pattern + minor convert updates --- ucan-key-support/Cargo.toml | 8 +- ucan/Cargo.toml | 42 ++--- ucan/src/chain.rs | 320 +++++++++++++++++++++++----------- ucan/src/crypto/did.rs | 3 +- ucan/src/ipld/signature.rs | 4 +- ucan/src/ipld/ucan.rs | 10 +- ucan/src/lib.rs | 13 +- ucan/src/tests/attenuation.rs | 67 ++++--- ucan/src/tests/chain.rs | 47 +++-- ucan/src/ucan.rs | 6 +- 10 files changed, 346 insertions(+), 174 deletions(-) diff --git a/ucan-key-support/Cargo.toml b/ucan-key-support/Cargo.toml index 0f83f4ad..6815b25a 100644 --- a/ucan-key-support/Cargo.toml +++ b/ucan-key-support/Cargo.toml @@ -20,22 +20,22 @@ version = "0.7.0-alpha.1" default = [] [dependencies] -ucan = {path = "../ucan", version = "0.7.0-alpha.1" } anyhow = "1.0.52" async-trait = "0.1.52" +bs58 = "0.4" ed25519-zebra = "^3" +log = "0.4" rsa = "0.6" sha2 = "0.10" -bs58 = "0.4" -log = "0.4" +ucan = { path = "../ucan", version = "0.7.0-alpha.1" } [build-dependencies] npm_rs = "0.2.1" [dev-dependencies] -rand = "~0.8" # NOTE: This is needed so that rand can be included in WASM builds getrandom = { version = "~0.2", features = ["js"] } +rand = "~0.8" wasm-bindgen-test = "~0.3" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] diff --git a/ucan/Cargo.toml b/ucan/Cargo.toml index d55de0d3..2f62fb32 100644 --- a/ucan/Cargo.toml +++ b/ucan/Cargo.toml @@ -20,33 +20,33 @@ edition = "2021" default = [] [dependencies] -cid = "~0.8" -anyhow = "^1" -async-trait = "~0.1" -async-recursion = "^1" -async-std = "^1" -serde_json = "^1" -serde = { version = "^1", features = ["derive"] } -base64 = "~0.13" -log = "~0.4" -url = "^2" -bs58 = "~0.4" -unsigned-varint = "~0.7" -libipld-core = { version = "~0.14", features = ["serde-codec", "serde"] } -libipld-json = "~0.14" -strum = "~0.24" -strum_macros = "~0.24" +anyhow = "1.0" +async-std = "1.0" +async-trait = "0.1" +base64 = "0.13" +bs58 = "0.4" +cid = "0.8" +futures = "0.3" instant = { version = "0.1", features = ["wasm-bindgen", "stdweb"] } -rand = "~0.8" +libipld-core = { version = "0.14", features = ["serde-codec", "serde"] } +libipld-json = "0.14" +log = "0.4" +rand = "0.8" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +strum = "0.24" +strum_macros = "0.24" +unsigned-varint = "0.7" +url = "2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] # NOTE: This is needed so that rand can be included in WASM builds -getrandom = { version = "~0.2", features = ["js"] } +getrandom = { version = "0.2", features = ["js"] } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tokio = { version = "^1", features = ["macros", "test-util"] } +tokio = { version = "1.0", features = ["macros", "test-util"] } [dev-dependencies] did-key = "0.1" -serde_ipld_dagcbor = "~0.2" -wasm-bindgen-test = "~0.3" +serde_ipld_dagcbor = "0.2" +wasm-bindgen-test = "0.3" diff --git a/ucan/src/chain.rs b/ucan/src/chain.rs index c26e63d5..5e5aa2fa 100644 --- a/ucan/src/chain.rs +++ b/ucan/src/chain.rs @@ -7,10 +7,27 @@ use crate::{ store::UcanJwtStore, ucan::Ucan, }; -use anyhow::{anyhow, Result}; -use async_recursion::async_recursion; +use anyhow::{anyhow, Context, Result}; use cid::Cid; -use std::{collections::BTreeSet, fmt::Debug}; +#[cfg(not(target_arch = "wasm32"))] +use futures::future::{BoxFuture, FutureExt}; +#[cfg(target_arch = "wasm32")] +use futures::future::{FutureExt, LocalBoxFuture}; +use std::{ + collections::BTreeSet, + convert::TryFrom, + fmt::{self, Debug}, + str::FromStr, + sync::Arc, +}; + +/// Type alias for handling recursive async fns, akin to async_recursion crate. +#[cfg(not(target_arch = "wasm32"))] +type BuildResult<'a, S> = BoxFuture<'a, Result>>; + +/// Type alias for handling recursive async fns in wasm, akin to async_recursion crate. +#[cfg(target_arch = "wasm32")] +type BuildResult<'a, S> = LocalBoxFuture<'a, Result>>; const PROOF_DELEGATION_SEMANTICS: ProofDelegationSemantics = ProofDelegationSemantics {}; @@ -38,130 +55,170 @@ where } /// A deserialized chain of ancestral proofs that are linked to a UCAN -#[derive(Debug)] -pub struct ProofChain { +pub struct ProofChain<'a, S> +where + S: UcanJwtStore, +{ ucan: Ucan, - proofs: Vec, + proofs: Vec>, redelegations: BTreeSet, + did_parser: Option>, + store: Option<&'a S>, } -impl ProofChain { - /// Instantiate a [ProofChain] from a [Ucan], given a [UcanJwtStore] and [DidParser] - #[cfg_attr(target_arch = "wasm32", async_recursion(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_recursion)] - pub async fn from_ucan( - ucan: Ucan, - did_parser: &mut DidParser, - store: &S, - ) -> Result - where - S: UcanJwtStore, - { - ucan.validate(did_parser).await?; - - let mut proofs: Vec = Vec::new(); - - for cid_string in ucan.proofs().iter() { - let cid = Cid::try_from(cid_string.as_str())?; - let ucan_token = store.require_token(&cid).await?; - let proof_chain = Self::try_from_token_string(&ucan_token, did_parser, store).await?; - proof_chain.validate_link_to(&ucan)?; - proofs.push(proof_chain); - } - - let mut redelegations = BTreeSet::::new(); - - for capability in CapabilityIterator::new(&ucan, &PROOF_DELEGATION_SEMANTICS) { - match capability.with() { - With::Resource { - kind: Resource::Scoped(ProofSelection::All), - } => { - for index in 0..proofs.len() { - redelegations.insert(index); - } - } - With::Resource { - kind: Resource::Scoped(ProofSelection::Index(index)), - } => { - if *index < proofs.len() { - redelegations.insert(*index); - } else { - return Err(anyhow!( - "Unable to redelegate proof; no proof at zero-based index {}", - index - )); - } - } - _ => continue, - } - } +impl Debug for ProofChain<'_, S> +where + S: UcanJwtStore, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ProofChain") + .field("ucan", &self.ucan) + .field("proofs", &self.proofs) + .field("redelegations", &self.redelegations) + .finish() + } +} - Ok(ProofChain { +impl<'a, S> ProofChain<'a, S> +where + S: UcanJwtStore, +{ + /// Instantiate a bare [ProofChain] from a [Ucan]. + pub fn from_ucan(ucan: Ucan) -> Self { + Self { ucan, - proofs, - redelegations, - }) + proofs: vec![], + redelegations: BTreeSet::::new(), + did_parser: None, + store: None, + } } - /// Instantiate a [ProofChain] from a [Cid], given a [UcanJwtStore] and [DidParser] - /// The [Cid] must resolve to a JWT token string - pub async fn from_cid(cid: &Cid, did_parser: &mut DidParser, store: &S) -> Result - where - S: UcanJwtStore, - { - Self::try_from_token_string(&store.require_token(cid).await?, did_parser, store).await + /// Validate and set [DidParser] for [ProofChain] build. + pub async fn with_parser(mut self, mut did_parser: DidParser) -> Result> { + self.ucan.validate(&mut did_parser).await?; + self.did_parser = Some(Arc::new(did_parser)); + Ok(self) } - /// Instantiate a [ProofChain] from a JWT token string, given a [UcanJwtStore] and [DidParser] - pub async fn try_from_token_string<'a, S>( - ucan_token_string: &str, - did_parser: &mut DidParser, - store: &S, - ) -> Result - where - S: UcanJwtStore, - { - let ucan = Ucan::try_from(ucan_token_string)?; - Self::from_ucan(ucan, did_parser, store).await + async fn with_parser_arc( + mut self, + mut did_parser: Arc, + ) -> Result> { + self.ucan.validate(Arc::make_mut(&mut did_parser)).await?; + self.did_parser = Some(did_parser); + Ok(self) } - fn validate_link_to(&self, ucan: &Ucan) -> Result<()> { - let audience = self.ucan.audience(); - let issuer = ucan.issuer(); + /// Set [UcanJwtStore] for [ProofChain] build. + pub fn with_store(mut self, store: &'a S) -> Self { + self.store = Some(store); + self + } - match audience == issuer { - true => match self.ucan.lifetime_encompasses(ucan) { - true => Ok(()), - false => Err(anyhow!("Invalid UCAN link: lifetime exceeds attenuation")), - }, - false => Err(anyhow!( - "Invalid UCAN link: audience {} does not match issuer {}", - audience, - issuer - )), - } + /// Instantiate a bare [ProofChain] from a [Cid], given a [UcanJwtStore]. + /// The [Cid] must resolve to a JWT token string and be available in the store. + pub async fn from_cid(cid: &Cid, store: &'a S) -> Result> { + let ucan_token = store.require_token(cid).await?; + Ok(Self { + ucan: Ucan::try_from(ucan_token)?, + proofs: vec![], + redelegations: BTreeSet::::new(), + did_parser: None, + store: Some(store), + }) } + /// Get the [ProofChain]'s ucan. pub fn ucan(&self) -> &Ucan { &self.ucan } - pub fn proofs(&self) -> &Vec { + /// Get the [ProofChain]'s proofs. + pub fn proofs(&self) -> &Vec { &self.proofs } - pub fn reduce_capabilities( + /// Build-out entire [ProofChain] after setting a [UcanJwtStore] and [DidParser]. + pub fn build(mut self) -> BuildResult<'a, S> { + let proof_chain = async move { + let did_parser = self + .did_parser + .take() + .context("No parser set for ProofChain")?; + + for cid_string in self.ucan.proofs().iter() { + let cid = Cid::try_from(cid_string.as_str())?; + + let proof_chain = + ProofChain::from_cid(&cid, self.store.context("No store set for ProofChain")?) + .await? + .with_parser_arc(Arc::clone(&did_parser)) + .await? + .build() + .await?; + + proof_chain.validate_link_to(&self.ucan)?; + self.proofs.push(proof_chain); + + for capability in CapabilityIterator::new(&self.ucan, &PROOF_DELEGATION_SEMANTICS) { + match capability.with() { + With::Resource { + kind: Resource::Scoped(ProofSelection::All), + } => { + for index in 0..self.proofs.len() { + self.redelegations.insert(index); + } + } + With::Resource { + kind: Resource::Scoped(ProofSelection::Index(index)), + } => { + if *index < self.proofs.len() { + self.redelegations.insert(*index); + } else { + return Err(anyhow!( + "Unable to redelegate proof; no proof at zero-based index {}", + index + )); + } + } + _ => continue, + } + } + } + + Ok(ProofChain { + ucan: self.ucan, + proofs: self.proofs, + redelegations: self.redelegations, + did_parser: Some(did_parser), + store: self.store, + }) + }; + + #[cfg(not(target_arch = "wasm32"))] + { + proof_chain.boxed() + } + + #[cfg(target_arch = "wasm32")] + { + proof_chain.boxed_local() + } + } + + pub fn reduce_capabilities( &self, semantics: &Semantics, - ) -> Vec> + ) -> Vec> where - Semantics: CapabilitySemantics, - S: Scope, + Semantics: CapabilitySemantics, + Sc: Scope, A: Action, { // Get the set of inherited attenuations (excluding redelegations) // before further attenuating by own lifetime and capabilities: - let ancestral_capability_infos: Vec> = self + let ancestral_capability_infos: Vec> = self .proofs .iter() .enumerate() @@ -176,7 +233,7 @@ impl ProofChain { // Get the set of capabilities that are blanket redelegated from // ancestor proofs (via the prf: resource): - let mut redelegated_capability_infos: Vec> = self + let mut redelegated_capability_infos: Vec> = self .redelegations .iter() .flat_map(|index| { @@ -199,16 +256,17 @@ impl ProofChain { // Get the claimed attenuations of this ucan, cross-checking ancestral // attenuations to discover the originating authority - let mut self_capability_infos: Vec> = match self.proofs.len() { - 0 => self_capabilities_iter + let mut self_capability_infos: Vec> = if self.proofs.is_empty() { + self_capabilities_iter .map(|capability| CapabilityInfo { originators: BTreeSet::from_iter(vec![self.ucan.issuer().to_string()]), capability, not_before: *self.ucan.not_before(), expires_at: *self.ucan.expires_at(), }) - .collect(), - _ => self_capabilities_iter + .collect() + } else { + self_capabilities_iter .map(|capability| { let mut originators = BTreeSet::::new(); @@ -235,12 +293,12 @@ impl ProofChain { expires_at: *self.ucan.expires_at(), } }) - .collect(), + .collect() }; self_capability_infos.append(&mut redelegated_capability_infos); - let mut merged_capability_infos = Vec::>::new(); + let mut merged_capability_infos = Vec::>::new(); // Merge redundant capabilities (accounting for redelegation), ensuring // that discrete originators are aggregated as we go @@ -262,4 +320,56 @@ impl ProofChain { merged_capability_infos } + + fn validate_link_to(&self, ucan: &Ucan) -> Result<()> { + let audience = self.ucan.audience(); + let issuer = ucan.issuer(); + + match audience == issuer { + true if self.ucan.lifetime_encompasses(ucan) => Ok(()), + true => Err(anyhow!("Invalid UCAN link: lifetime exceeds attenuation")), + false => Err(anyhow!( + "Invalid UCAN link: audience {} does not match issuer {}", + audience, + issuer + )), + } + } +} + +/// Instantiate a [ProofChain] from a JWT token string reference. +impl<'a, S> TryFrom<&'a str> for ProofChain<'a, S> +where + S: UcanJwtStore, +{ + type Error = anyhow::Error; + + fn try_from(ucan_token: &str) -> Result { + ProofChain::from_str(ucan_token) + } +} + +/// Instantiate a [ProofChain] from a JWT token owned string. +impl TryFrom for ProofChain<'_, S> +where + S: UcanJwtStore, +{ + type Error = anyhow::Error; + + fn try_from(ucan_token: String) -> Result { + ProofChain::from_str(ucan_token.as_str()) + } +} + +/// Instantiate a [ProofChain] from a JWT token owned string reference. +impl FromStr for ProofChain<'_, S> +where + S: UcanJwtStore, +{ + type Err = anyhow::Error; + + fn from_str(ucan_token: &str) -> Result { + let ucan = Ucan::try_from(ucan_token)?; + Ok(Self::from_ucan(ucan)) + } } diff --git a/ucan/src/crypto/did.rs b/ucan/src/crypto/did.rs index a3178bef..191054f9 100644 --- a/ucan/src/crypto/did.rs +++ b/ucan/src/crypto/did.rs @@ -19,9 +19,10 @@ pub const P256_MAGIC_BYTES: &[u8] = &[0x80, 0x24]; pub const SECP256K1_MAGIC_BYTES: &[u8] = &[0xe7, 0x1]; /// A parser that is able to convert from a DID string into a corresponding -/// [`crypto::SigningKey`] implementation. The parser extracts the signature +/// [`KeyMaterial`] implementation. The parser extracts the signature /// magic bytes from a given DID and tries to match them to a corresponding /// constructor function that produces a `SigningKey`. +#[derive(Clone)] pub struct DidParser { key_constructors: KeyConstructors, key_cache: KeyCache, diff --git a/ucan/src/ipld/signature.rs b/ucan/src/ipld/signature.rs index 1cb27f55..23fec7de 100644 --- a/ucan/src/ipld/signature.rs +++ b/ucan/src/ipld/signature.rs @@ -20,7 +20,7 @@ const EIP191_VARSIG_PREFIX: u64 = 0xd191; /// counterpart representation and vice-versa /// Note, not all valid JWT signature algorithms are represented by this /// library, nor are all valid varsig prefixes -/// See https://github.com/ucan-wg/ucan-ipld#25-signature +/// See #[derive(Debug, Eq, PartialEq)] pub enum VarsigPrefix { NonStandard, @@ -116,7 +116,7 @@ impl TryFrom for VarsigPrefix { /// An envelope for the UCAN-IPLD-equivalent of a UCAN's JWT signature, which /// is a specified prefix in front of the raw signature bytes -/// See: https://github.com/ucan-wg/ucan-ipld#25-signature +/// See: #[repr(transparent)] #[derive(Serialize, Deserialize)] pub struct Signature(pub Vec); diff --git a/ucan/src/ipld/ucan.rs b/ucan/src/ipld/ucan.rs index 4477dde4..050b27f8 100644 --- a/ucan/src/ipld/ucan.rs +++ b/ucan/src/ipld/ucan.rs @@ -8,7 +8,7 @@ use crate::{ use cid::Cid; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::{convert::TryFrom, str::FromStr}; +use std::{borrow::Borrow, convert::TryFrom, str::FromStr}; #[derive(Serialize, Deserialize)] pub struct UcanIpld { @@ -27,6 +27,14 @@ pub struct UcanIpld { pub nbf: Option, } +impl TryFrom for UcanIpld { + type Error = anyhow::Error; + + fn try_from(ucan: Ucan) -> Result { + ucan.borrow().try_into() + } +} + impl TryFrom<&Ucan> for UcanIpld { type Error = anyhow::Error; diff --git a/ucan/src/lib.rs b/ucan/src/lib.rs index 7cbd8998..c9b3066a 100644 --- a/ucan/src/lib.rs +++ b/ucan/src/lib.rs @@ -11,7 +11,7 @@ //! This crate offers the [`builder::UcanBuilder`] abstraction to generate //! signed UCAN tokens. //! -//! To generate a signed token, you need to provide a [`crypto::SigningKey`] +//! To generate a signed token, you need to provide a [`crypto::KeyMaterial`] //! implementation. For more information on providing a signing key, see the //! [`crypto`] module documentation. //! @@ -52,16 +52,21 @@ //! // You must bring your own key support //! ]; //! -//! async fn get_capabilities<'a, Semantics, S, A, Store>(ucan_token: &'a str, semantics: &'a Semantics, store: &'a Store) -> Result>, anyhow::Error> +//! async fn get_capabilities<'a, Semantics, S, A, Store>(ucan_token: &'a str, semantics: &'a Semantics, store: Store) -> Result>, anyhow::Error> //! where //! Semantics: CapabilitySemantics, //! S: Scope, //! A: Action, -//! Store: UcanJwtStore +//! Store: UcanJwtStore + Default //! { //! let mut did_parser = DidParser::new(SUPPORTED_KEY_TYPES); //! -//! Ok(ProofChain::try_from_token_string(ucan_token, &mut did_parser, store).await? +//! Ok(ProofChain::try_from(ucan_token)? +//! .with_parser(did_parser) +//! .await? +//! .with_store(&store) +//! .build() +//! .await? //! .reduce_capabilities(semantics)) //! } //! ``` diff --git a/ucan/src/tests/attenuation.rs b/ucan/src/tests/attenuation.rs index a4d58852..2fc3ab0d 100644 --- a/ucan/src/tests/attenuation.rs +++ b/ucan/src/tests/attenuation.rs @@ -18,7 +18,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_works_with_a_simple_example() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let email_semantics = EmailSemantics {}; let send_email_as_alice = email_semantics @@ -56,10 +56,15 @@ pub async fn it_works_with_a_simple_example() { .await .unwrap(); - let chain = - ProofChain::try_from_token_string(attenuated_token.as_str(), &mut did_parser, &store) - .await - .unwrap(); + let chain = ProofChain::try_from(attenuated_token.as_str()) + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .with_store(&store) + .build() + .await + .unwrap(); let capability_infos = chain.reduce_capabilities(&email_semantics); @@ -78,11 +83,11 @@ pub async fn it_works_with_a_simple_example() { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_reports_the_first_issuer_in_the_chain_as_originator() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let email_semantics = EmailSemantics {}; let send_email_as_bob = email_semantics - .parse("mailto:bob@email.com".into(), "email/send".into()) + .parse("mailto:bob@email.com", "email/send") .unwrap(); let leaf_ucan = UcanBuilder::default() @@ -115,7 +120,13 @@ pub async fn it_reports_the_first_issuer_in_the_chain_as_originator() { .await .unwrap(); - let capability_infos = ProofChain::try_from_token_string(&ucan_token, &mut did_parser, &store) + let capability_infos = ProofChain::try_from(ucan_token) + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .with_store(&store) + .build() .await .unwrap() .reduce_capabilities(&email_semantics); @@ -135,14 +146,14 @@ pub async fn it_reports_the_first_issuer_in_the_chain_as_originator() { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_finds_the_right_proof_chain_for_the_originator() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let email_semantics = EmailSemantics {}; let send_email_as_bob = email_semantics - .parse("mailto:bob@email.com".into(), "email/send".into()) + .parse("mailto:bob@email.com", "email/send") .unwrap(); let send_email_as_alice = email_semantics - .parse("mailto:alice@email.com".into(), "email/send".into()) + .parse("mailto:alice@email.com", "email/send") .unwrap(); let leaf_ucan_alice = UcanBuilder::default() @@ -193,9 +204,16 @@ pub async fn it_finds_the_right_proof_chain_for_the_originator() { .await .unwrap(); - let proof_chain = ProofChain::try_from_token_string(&ucan_token, &mut did_parser, &store) + let proof_chain = ProofChain::try_from(ucan_token.as_str()) + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .with_store(&store) + .build() .await .unwrap(); + let capability_infos = proof_chain.reduce_capabilities(&email_semantics); assert_eq!(capability_infos.len(), 2); @@ -208,8 +226,8 @@ pub async fn it_finds_the_right_proof_chain_for_the_originator() { &CapabilityInfo { originators: BTreeSet::from_iter(vec![identities.alice_did]), capability: send_email_as_alice, - not_before: ucan.not_before().clone(), - expires_at: ucan.expires_at().clone() + not_before: *ucan.not_before(), + expires_at: *ucan.expires_at() } ); @@ -218,8 +236,8 @@ pub async fn it_finds_the_right_proof_chain_for_the_originator() { &CapabilityInfo { originators: BTreeSet::from_iter(vec![identities.bob_did]), capability: send_email_as_bob, - not_before: ucan.not_before().clone(), - expires_at: ucan.expires_at().clone() + not_before: *ucan.not_before(), + expires_at: *ucan.expires_at() } ); } @@ -228,11 +246,11 @@ pub async fn it_finds_the_right_proof_chain_for_the_originator() { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_reports_all_chain_options() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let email_semantics = EmailSemantics {}; let send_email_as_alice = email_semantics - .parse("mailto:alice@email.com".into(), "email/send".into()) + .parse("mailto:alice@email.com", "email/send") .unwrap(); let leaf_ucan_alice = UcanBuilder::default() @@ -282,9 +300,16 @@ pub async fn it_reports_all_chain_options() { .await .unwrap(); - let proof_chain = ProofChain::try_from_token_string(&ucan_token, &mut did_parser, &store) + let proof_chain = ProofChain::try_from(ucan_token) + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .with_store(&store) + .build() .await .unwrap(); + let capability_infos = proof_chain.reduce_capabilities(&email_semantics); assert_eq!(capability_infos.len(), 1); @@ -296,8 +321,8 @@ pub async fn it_reports_all_chain_options() { &CapabilityInfo { originators: BTreeSet::from_iter(vec![identities.alice_did, identities.bob_did]), capability: send_email_as_alice, - not_before: ucan.not_before().clone(), - expires_at: ucan.expires_at().clone() + not_before: *ucan.not_before(), + expires_at: *ucan.expires_at() } ); } diff --git a/ucan/src/tests/chain.rs b/ucan/src/tests/chain.rs index ddb616d6..397e379a 100644 --- a/ucan/src/tests/chain.rs +++ b/ucan/src/tests/chain.rs @@ -16,7 +16,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_decodes_deep_ucan_chains() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let leaf_ucan = UcanBuilder::default() .issued_by(&identities.alice_key) @@ -47,10 +47,15 @@ pub async fn it_decodes_deep_ucan_chains() { .await .unwrap(); - let chain = - ProofChain::try_from_token_string(delegated_token.as_str(), &mut did_parser, &store) - .await - .unwrap(); + let chain = ProofChain::try_from(delegated_token) + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .with_store(&store) + .build() + .await + .unwrap(); assert_eq!(chain.ucan().audience(), &identities.mallory_did); assert_eq!( @@ -63,7 +68,7 @@ pub async fn it_decodes_deep_ucan_chains() { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_fails_with_incorrect_chaining() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let leaf_ucan = UcanBuilder::default() .issued_by(&identities.alice_key) @@ -94,8 +99,14 @@ pub async fn it_fails_with_incorrect_chaining() { .await .unwrap(); - let parse_token_result = - ProofChain::try_from_token_string(delegated_token.as_str(), &mut did_parser, &store).await; + let parse_token_result = ProofChain::try_from(delegated_token) + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .with_store(&store) + .build() + .await; assert!(parse_token_result.is_err()); } @@ -104,7 +115,7 @@ pub async fn it_fails_with_incorrect_chaining() { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_can_be_instantiated_by_cid() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let leaf_ucan = UcanBuilder::default() .issued_by(&identities.alice_key) @@ -138,7 +149,13 @@ pub async fn it_can_be_instantiated_by_cid() { let cid = store.write_token(&delegated_token).await.unwrap(); - let chain = ProofChain::from_cid(&cid, &mut did_parser, &store) + let chain = ProofChain::from_cid(&cid, &store) + .await + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .build() .await .unwrap(); @@ -153,7 +170,7 @@ pub async fn it_can_be_instantiated_by_cid() { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] pub async fn it_can_handle_multiple_leaves() { let identities = Identities::new().await; - let mut did_parser = DidParser::new(SUPPORTED_KEYS); + let did_parser = DidParser::new(SUPPORTED_KEYS); let leaf_ucan_1 = UcanBuilder::default() .issued_by(&identities.alice_key) @@ -199,7 +216,13 @@ pub async fn it_can_handle_multiple_leaves() { .await .unwrap(); - ProofChain::try_from_token_string(&delegated_token, &mut did_parser, &store) + ProofChain::try_from(delegated_token) + .unwrap() + .with_parser(did_parser) + .await + .unwrap() + .with_store(&store) + .build() .await .unwrap(); } diff --git a/ucan/src/ucan.rs b/ucan/src/ucan.rs index 7bbb0aae..9f991e19 100644 --- a/ucan/src/ucan.rs +++ b/ucan/src/ucan.rs @@ -194,7 +194,7 @@ impl TryFrom for Cid { } } -/// Deserialize an encoded UCAN token string reference into a UCAN +/// Deserialize an encoded UCAN token string reference into a UCAN. impl<'a> TryFrom<&'a str> for Ucan { type Error = anyhow::Error; @@ -203,7 +203,7 @@ impl<'a> TryFrom<&'a str> for Ucan { } } -/// Deserialize an encoded UCAN token string into a UCAN +/// Deserialize an encoded UCAN token string into a UCAN. impl TryFrom for Ucan { type Error = anyhow::Error; @@ -212,7 +212,7 @@ impl TryFrom for Ucan { } } -/// Deserialize an encoded UCAN token string reference into a UCAN +/// Deserialize an encoded UCAN token string reference into a UCAN. impl FromStr for Ucan { type Err = anyhow::Error;