From faba286d43af0555af01d2c7a57bbf1115077bcc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 22 Mar 2024 14:04:22 -0700 Subject: [PATCH 1/4] Test delegation store --- src/delegation/agent.rs | 33 +- src/delegation/policy/predicate.rs | 32 +- src/delegation/store/memory.rs | 528 +++++++++++++++++++++++++++-- src/delegation/store/traits.rs | 42 ++- src/invocation/agent.rs | 83 ++--- 5 files changed, 601 insertions(+), 117 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index bfee4119..8165b0a9 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -20,11 +20,15 @@ use web_time::SystemTime; /// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] pub struct Agent< - S: Store, - DID: Did = did::preset::Verifier, - V: varsig::Header + Clone = varsig::header::Preset, - Enc: Codec + Into + TryFrom = varsig::encoding::Preset, -> { + S: Store, + DID: Did + Clone = did::preset::Verifier, + V: varsig::Header + Clone = varsig::header::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, +> where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, +{ /// The [`Did`][Did] of the agent. pub did: DID, @@ -32,17 +36,18 @@ pub struct Agent< pub store: S, signer: ::Signer, - _marker: PhantomData<(V, Enc)>, + _marker: PhantomData<(V, C)>, } impl< - S: Store + Clone, + S: Store + Clone, DID: Did + Clone, - V: varsig::Header + Clone, - Enc: Codec + TryFrom + Into, - > Agent + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Agent where - Ipld: Encode, + Ipld: Encode, + Delegation: Encode, Payload: TryFrom>, Named: From>, { @@ -67,7 +72,7 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -121,7 +126,7 @@ where pub fn receive( &self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, + delegation: Delegation, ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); @@ -135,7 +140,7 @@ where .validate_signature() .map_err(|_| ReceiveError::InvalidSignature(cid))?; - self.store.insert(cid, delegation).map_err(Into::into) + self.store.insert_keyed(cid, delegation).map_err(Into::into) } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 423d9303..12cb7eae 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -79,24 +79,20 @@ impl Predicate { Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, - Predicate::Every(xs, p) => { - xs.get(data)? - .to_vec() - .iter() - .try_fold(true, |acc, each_datum| { - dbg!("every", &p, acc, each_datum); - Ok(acc && p.clone().run(&each_datum.0)?) - })? - } - Predicate::Some(xs, p) => { - xs.get(data)? - .to_vec() - .iter() - .try_fold(false, |acc, each_datum| { - dbg!("some", &p, acc, each_datum); - Ok(acc || p.clone().run(&each_datum.0)?) - })? - } + Predicate::Every(xs, p) => xs + .get(data)? + .to_vec() + .iter() + .try_fold(true, |acc, each_datum| { + Ok(acc && p.clone().run(&each_datum.0)?) + })?, + Predicate::Some(xs, p) => xs + .get(data)? + .to_vec() + .iter() + .try_fold(false, |acc, each_datum| { + Ok(acc || p.clone().run(&each_datum.0)?) + })?, }) } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index a7bc74e6..d9dcb376 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -105,7 +105,7 @@ impl, C: Codec + TryFrom + Into bool { - self.read().ucans.is_empty() // FIXME acocunt for revocations? + self.read().ucans.is_empty() // FIXME account for revocations? } fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { @@ -173,7 +173,7 @@ where // FIXME } - fn insert( + fn insert_keyed( &self, cid: Cid, delegation: Delegation, @@ -203,7 +203,7 @@ where aud: &DID, subject: &Option, command: String, - policy: Vec, // FIXME + policy: Vec, now: SystemTime, ) -> Result>)>>, Self::DelegationStoreError> { @@ -216,7 +216,8 @@ where let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); - let mut parent_candidate_stack = vec![]; + let mut parent_candidate_stack = + vec![sub_candidates.iter().chain(powerline_candidates.iter())]; let mut hypothesis_chain = vec![]; let corrected_target_command = if command.ends_with('/') { @@ -225,18 +226,23 @@ where format!("{}/", command) }; - parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); - let mut next = None; + // TODO Vec> + // parent_candidate_stack.push(sub_candidates.iter()); // .chain(powerline_candidates.iter())); + + // Pseudocode: + // If empty, pop: + // if pop fials, you're out of stuff + // If 'outer: loop { if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { if parent_cid_candidates.clone().collect::>().is_empty() { parent_candidate_stack.pop(); - hypothesis_chain.pop(); - break 'outer; + continue; } 'inner: for cid in parent_cid_candidates { + // CHECKS if read_tx.revocations.contains(cid) { continue; } @@ -258,17 +264,19 @@ where continue; } - for target_pred in policy.iter() { - for delegate_pred in delegation.payload.policy.iter() { - let comparison = - target_pred.harmonize(delegate_pred, vec![], vec![]); + // FIXME + // for target_pred in policy.iter() { + // for delegate_pred in delegation.payload.policy.iter() { + // let comparison = + // target_pred.harmonize(delegate_pred, vec![], vec![]); - if comparison.is_conflict() || comparison.is_lhs_weaker() { - continue 'inner; - } - } - } + // if comparison.is_conflict() || comparison.is_lhs_weaker() { + // continue 'inner; + // } + // } + // } + // PASSED CHECKS, so processing hypothesis_chain.push((cid.clone(), Arc::clone(delegation))); let issuer = delegation.issuer().clone(); @@ -281,34 +289,486 @@ where let new_aud_candidates = all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); - let new_powerline_candidates = - all_powerlines.get(&issuer).unwrap_or(&blank_set); - - if !new_aud_candidates.is_empty() || !new_powerline_candidates.is_empty() { - next = Some( - new_aud_candidates - .iter() - .chain(new_powerline_candidates.iter()), + if !new_aud_candidates.is_empty() || !all_powerlines.get(&issuer).is_none() + { + parent_candidate_stack.push( + new_aud_candidates.iter().chain( + all_powerlines.get(&issuer).unwrap_or(&blank_set).iter(), + ), ); break 'inner; } } } - - if let Some(ref n) = next { - parent_candidate_stack.push(n.clone()); - next = None; - } else { - // Didn't find a match - break 'outer; - } } else { parent_candidate_stack.pop(); - hypothesis_chain.pop(); + break 'outer; } } Ok(NonEmpty::from_vec(hypothesis_chain)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::varsig::encoding; + use crate::crypto::varsig::header; + use crate::{crypto::signature::Envelope, delegation::store::Store}; + + use libipld_core::cid::Cid; + use nonempty::nonempty; + use pretty_assertions as pretty; + use rand::thread_rng; + use std::time::SystemTime; + use testresult::TestResult; + + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { + let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let verifier = + crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa(sk.verifying_key())); + let signer = crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(sk)); + + (verifier, signer) + } + + #[test_log::test] + fn test_get_fail() -> TestResult { + let store = crate::delegation::store::MemoryStore::default(); + store.get(&Cid::default())?; + pretty::assert_eq!(store.get(&Cid::default()), Ok(None)); + Ok(()) + } + + #[test_log::test] + fn test_insert_get_roundtrip() -> TestResult { + let (did, signer) = gen_did(); + + let store = MemoryStore::default(); + let varsig_header = header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }); + + let deleg = Delegation::try_sign( + &signer, + varsig_header, + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(did.clone()) + .audience(did.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + let retrieved = store.get(&deleg.cid()?)?.ok_or("failed to retrieve")?; + + pretty::assert_eq!(deleg, *retrieved); + + Ok(()) + } + + #[test_log::test] + fn test_insert_is_idempotent() -> TestResult { + let (did, signer) = gen_did(); + + let store = MemoryStore::default(); + let varsig_header = header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }); + + let deleg = Delegation::try_sign( + &signer, + varsig_header, + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(did.clone()) + .audience(did.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + + let retrieved = store.get(&deleg.cid()?)?.ok_or("failed to retrieve")?; + + pretty::assert_eq!(deleg, *retrieved); + pretty::assert_eq!(store.len(), 1); + + Ok(()) + } + + mod get_chain { + use super::*; + + #[test_log::test] + fn test_simple_fail() -> TestResult { + let (server, _server_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let got = store.get_chain(&server, &None, "/".into(), vec![], SystemTime::now())?; + + pretty::assert_eq!(got, None); + Ok(()) + } + + #[test_log::test] + fn test_with_one() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, _bob_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + + let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); + Ok(()) + } + + #[test_log::test] + fn test_with_one_with_others_in_store() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let noise = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/example".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(noise.clone())?; + + let deleg = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + + let more_noise = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(carol.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(more_noise.clone())?; + + let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); + Ok(()) + } + + #[test_log::test] + fn test_with_two() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg_1 = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_1.clone())?; + + let deleg_2 = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_2.clone())?; + + let got = + store.get_chain(&carol, &Some(alice), "/".into(), vec![], SystemTime::now())?; + + pretty::assert_eq!( + got, + Some( + nonempty![ + (deleg_2.cid()?, Arc::new(deleg_2)), + (deleg_1.cid()?, Arc::new(deleg_1)), + ] + .into() + ) + ); + Ok(()) + } + + #[test_log::test] + fn test_looking_for_narrower_command() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg_1 = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_1.clone())?; + + let deleg_2 = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/test/me".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_2.clone())?; + + let got = store.get_chain( + &carol, + &Some(alice), + "/test/me/now".into(), + vec![], + SystemTime::now(), + )?; + + pretty::assert_eq!( + got, + Some( + nonempty![ + (deleg_2.cid()?, Arc::new(deleg_2)), + (deleg_1.cid()?, Arc::new(deleg_1)), + ] + .into() + ) + ); + Ok(()) + } + + #[test_log::test] + fn test_broken_chain() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dan, dan_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(alice_to_bob.clone())?; + + let carol_to_dan = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(carol.clone()) + .audience(dan.clone()) + .command("/test/me".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(carol_to_dan.clone())?; + + let got = store.get_chain( + &carol, + &Some(alice), + "/test/me/now".into(), + vec![], + SystemTime::now(), + )?; + + pretty::assert_eq!(got, None); + Ok(()) + } + + #[test_log::test] + fn test_long_chain() -> TestResult { + // Scenario + // ======== + // 1. bob -*-> carol + // 2. carol -a-> dave + // 3. alice -d-> bob + // + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dave, _dave_signer) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let store = crate::delegation::store::MemoryStore::default(); + + // 1. bob -*-> carol + let bob_to_carol = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. carol -a-> dave + let carol_to_dave = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), // FIXME can also put this on a builder + crate::delegation::PayloadBuilder::default() + .subject(None) // FIXME needs a sibject when we figure out powerbox + .issuer(carol.clone()) + .audience(dave.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. alice -d-> bob + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(bob_to_carol.clone())?; + store.insert(carol_to_dave.clone())?; + store.insert(alice_to_bob.clone())?; + + let got: Vec = store + .get_chain(&dave, &Some(alice), "/".into(), vec![], SystemTime::now()) + .map_err(|e| e.to_string())? + .ok_or("failed during proof lookup")? + .iter() + .map(|(cid, _)| cid) + .cloned() + .collect(); + + pretty::assert_eq!( + got, + vec![ + carol_to_dave.cid()?, + bob_to_carol.cid()?, + alice_to_bob.cid()? + ] + ); + + Ok(()) + } + } +} diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 7413e700..d19b5d13 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,25 +1,39 @@ use crate::{ + ability::arguments::Named, + crypto::signature::Envelope, crypto::varsig, + delegation::payload::Payload, delegation::{policy::Predicate, Delegation}, did::Did, }; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::{fmt::Debug, sync::Arc}; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store + Clone, C: Codec + TryFrom + Into> +where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, +{ type DelegationStoreError: Debug; fn get( &self, cid: &Cid, - ) -> Result>>, Self::DelegationStoreError>; + ) -> Result>>, Self::DelegationStoreError>; + + fn insert(&self, delegation: Delegation) -> Result<(), Self::DelegationStoreError> { + self.insert_keyed(delegation.cid().expect("FIXME"), delegation) + } - fn insert( + fn insert_keyed( &self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation @@ -34,7 +48,7 @@ pub trait Store, Enc: Codec + TryFrom + In command: String, policy: Vec, now: SystemTime, - ) -> Result>)>>, Self::DelegationStoreError>; + ) -> Result>)>>, Self::DelegationStoreError>; fn get_chain_cids( &self, @@ -63,15 +77,23 @@ pub trait Store, Enc: Codec + TryFrom + In fn get_many( &self, cids: &[Cid], - ) -> Result>>>, Self::DelegationStoreError> { + ) -> Result>>>, Self::DelegationStoreError> { cids.iter() .map(|cid| self.get(cid)) .collect::>() } } -impl, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> - Store for &T +impl< + T: Store, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Store for &T +where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, { type DelegationStoreError = >::DelegationStoreError; @@ -82,12 +104,12 @@ impl, DID: Did, V: varsig::Header, C: Codec + TryFrom, ) -> Result<(), Self::DelegationStoreError> { - (**self).insert(cid, delegation) + (**self).insert_keyed(cid, delegation) } fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 5641a169..11c3de80 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,6 +6,7 @@ use super::{ use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::ability::parse::ParseAbility; +use crate::delegation::Delegation; use crate::invocation::payload::PayloadBuilder; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, @@ -23,12 +24,7 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt, - marker::PhantomData, - sync::Arc, -}; +use std::{collections::BTreeMap, fmt, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -37,10 +33,14 @@ pub struct Agent< S: Store, D: delegation::store::Store, T: ToCommand = ability::preset::Preset, - DID: Did = did::preset::Verifier, + DID: Did + Clone = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, -> { +> where + Delegation: Encode, + delegation::Payload: TryFrom>, + Named: From>, +{ /// The agent's [`DID`]. pub did: DID, @@ -68,6 +68,9 @@ where C: Codec + Into + TryFrom, >::InvocationStoreError: fmt::Debug, >::DelegationStoreError: fmt::Debug, + delegation::Payload: TryFrom>, + Named: From>, + Delegation: Encode, { pub fn new( did: DID, @@ -96,20 +99,21 @@ where now: SystemTime, varsig_header: V, ) -> Result, InvokeError> { - let proofs = self - .delegation_store - .get_chain( - &self.did, - &Some(subject.clone()), - ability.to_command(), - vec![], - now, - ) - .map_err(InvokeError::DelegationStoreError)? - .map(|chain| chain.map(|(cid, _)| cid).into()) - .unwrap_or(vec![]); - - let mut seed = vec![]; + let proofs = if subject == self.did { + vec![] + } else { + self.delegation_store + .get_chain( + &self.did, + &Some(subject.clone()), + ability.to_command(), + vec![], + now, + ) + .map_err(InvokeError::DelegationStoreError)? + .map(|chain| chain.map(|(cid, _)| cid).into()) + .unwrap_or(vec![]) // FIXME + }; let payload = Payload { issuer: self.did.clone(), @@ -118,7 +122,7 @@ where ability, proofs, metadata, - nonce: Nonce::generate_12(&mut seed), + nonce: Nonce::generate_12(&mut vec![]), cause, expiration, issued_at, @@ -441,7 +445,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_implicit_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( None, @@ -469,7 +473,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_and_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( Some(agent.did.clone()), @@ -498,7 +502,7 @@ mod tests { #[test_log::test] fn test_other_recipient() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let (not_server, _) = gen_did(); @@ -528,7 +532,7 @@ mod tests { #[test_log::test] fn test_expired() -> TestResult { let (past, now, _exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( None, @@ -563,7 +567,7 @@ mod tests { #[test_log::test] fn test_invalid_sig() -> TestResult { let (_past, now, _exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let server = &agent.did; let mut invocation = agent.invoke( @@ -686,13 +690,13 @@ mod tests { .build()?, )?; - drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); - drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); - drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); + del_store.insert(account_device_ucan.clone())?; + del_store.insert(account_pbox.clone())?; + del_store.insert(dnslink_ucan.clone())?; let proofs_for_powerline: Vec = del_store .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? - .ok_or("FIXME")? + .ok_or("failed during proof lookup")? .iter() .map(|x| x.0.clone()) .collect(); @@ -714,17 +718,14 @@ mod tests { .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(vec![ - account_device_ucan.cid()?, - account_pbox.cid()?, - dnslink_ucan.cid()?, - ]) - // .proofs(proofs_for_powerline.clone()) + .proofs(proofs_for_powerline.clone()) .build()?, )?; let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink?.ok_or("FIXME")?.len(); + let dnslink_len = chain_for_dnslink? + .ok_or("failed while finding DNSLink delegtaions")? + .len(); Ok(Ctx { varsig_header, @@ -751,7 +752,7 @@ mod tests { fn test_chain_ok() -> TestResult { let ctx = setup_test_chain()?; - let mut agent = Agent::new( + let agent = Agent::new( ctx.server.clone(), ctx.server_signer.clone(), &ctx.inv_store, @@ -767,7 +768,7 @@ mod tests { fn test_chain_wrong_sub() -> TestResult { let ctx = setup_test_chain()?; - let mut agent = Agent::new( + let agent = Agent::new( ctx.server.clone(), ctx.server_signer.clone(), &ctx.inv_store, From 246578b7b7f62921e930296d96b1e142e9225892 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 23 Mar 2024 15:45:16 -0700 Subject: [PATCH 2/4] WIP testing --- src/crypto/signature/envelope.rs | 4 +- src/delegation/agent.rs | 3 +- src/delegation/store/memory.rs | 125 +++++++++++++++++++++++++--- src/delegation/store/traits.rs | 4 +- src/invocation/agent.rs | 29 ++++--- src/invocation/store.rs | 135 +------------------------------ src/invocation/store/memory.rs | 83 +++++++++++++++++++ src/invocation/store/traits.rs | 51 ++++++++++++ 8 files changed, 279 insertions(+), 155 deletions(-) create mode 100644 src/invocation/store/memory.rs create mode 100644 src/invocation/store/traits.rs diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 0cea6c02..170b719d 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -186,11 +186,11 @@ pub trait Envelope: Sized { fn cid(&self) -> Result where - Self: Encode, + Ipld: Encode, { let codec = varsig::header::Header::codec(self.varsig_header()).clone(); let mut ipld_buffer = vec![]; - self.encode(codec, &mut ipld_buffer)?; + self.to_ipld_envelope().encode(codec, &mut ipld_buffer)?; let multihash = Code::Sha2_256.digest(&ipld_buffer); Ok(Cid::new_v1( diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 8165b0a9..16482c14 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -25,7 +25,7 @@ pub struct Agent< V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, > where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { @@ -47,7 +47,6 @@ impl< > Agent where Ipld: Encode, - Delegation: Encode, Payload: TryFrom>, Named: From>, { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index d9dcb376..9dcf04b3 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -160,7 +160,7 @@ impl< where Named: From>, delegation::Payload: TryFrom>, - Delegation: Encode, + Ipld: Encode, { type DelegationStoreError = Infallible; @@ -235,19 +235,28 @@ where // If 'outer: loop { + dbg!("OUTER"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { + dbg!("SOME INNER"); + for cid in parent_cid_candidates.clone() { + dbg!("INNER", cid.to_string()); + } if parent_cid_candidates.clone().collect::>().is_empty() { + dbg!("EMPTY"); parent_candidate_stack.pop(); continue; } 'inner: for cid in parent_cid_candidates { + dbg!("BBBBBBBBBBBBBBBBBBBBBB"); + dbg!(cid.to_string()); // CHECKS if read_tx.revocations.contains(cid) { continue; } if let Some(delegation) = read_tx.ucans.get(cid) { + dbg!("EEEEEEEEEEEEEEE"); if delegation.check_time(now).is_err() { continue; } @@ -283,6 +292,7 @@ where // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() { + dbg!("HHHHHHHHHHHH"); break 'outer; } @@ -336,7 +346,11 @@ mod tests { #[test_log::test] fn test_get_fail() -> TestResult { - let store = crate::delegation::store::MemoryStore::default(); + let store = MemoryStore::< + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + >::default(); store.get(&Cid::default())?; pretty::assert_eq!(store.get(&Cid::default()), Ok(None)); Ok(()) @@ -415,7 +429,11 @@ mod tests { fn test_simple_fail() -> TestResult { let (server, _server_signer) = gen_did(); - let store = crate::delegation::store::MemoryStore::default(); + let store = MemoryStore::< + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + >::default(); let got = store.get_chain(&server, &None, "/".into(), vec![], SystemTime::now())?; pretty::assert_eq!(got, None); @@ -635,9 +653,9 @@ mod tests { #[test_log::test] fn test_broken_chain() -> TestResult { let (alice, alice_signer) = gen_did(); - let (bob, bob_signer) = gen_did(); + let (bob, _bob_signer) = gen_did(); let (carol, carol_signer) = gen_did(); - let (dan, dan_signer) = gen_did(); + let (dan, _dan_signer) = gen_did(); let store = crate::delegation::store::MemoryStore::default(); let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( @@ -693,11 +711,10 @@ mod tests { // 1. bob -*-> carol // 2. carol -a-> dave // 3. alice -d-> bob - // let (alice, alice_signer) = gen_did(); let (bob, bob_signer) = gen_did(); let (carol, carol_signer) = gen_did(); - let (dave, _dave_signer) = gen_did(); + let (dave, _) = gen_did(); let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( crate::crypto::varsig::header::EdDsaHeader { @@ -723,9 +740,9 @@ mod tests { // 2. carol -a-> dave let carol_to_dave = crate::Delegation::try_sign( &carol_signer, - varsig_header.clone(), // FIXME can also put this on a builder + varsig_header.clone(), crate::delegation::PayloadBuilder::default() - .subject(None) // FIXME needs a sibject when we figure out powerbox + .subject(None) .issuer(carol.clone()) .audience(dave.clone()) .command("/".into()) @@ -770,5 +787,95 @@ mod tests { Ok(()) } + + #[test_log::test] + fn test_long_powerline() -> TestResult { + // Scenario + // ======== + // 1. bob -*-> carol + // 2. carol -a-> dave + // 3. alice -d-> bob + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dave, _) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let store = crate::delegation::store::MemoryStore::default(); + + // 1. bob -*-> carol + let bob_to_carol = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. carol -a-> dave + let carol_to_dave = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(carol.clone()) + .audience(dave.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. alice -d-> bob + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(bob_to_carol.clone())?; + store.insert(carol_to_dave.clone())?; + store.insert(alice_to_bob.clone())?; + + let got: Vec = store + .get_chain(&dave, &None, "/".into(), vec![], SystemTime::now()) + .map_err(|e| e.to_string())? + .ok_or("failed during proof lookup")? + .iter() + .map(|(cid, _)| cid) + .cloned() + .collect(); + + dbg!("THERE!!!!!!!!!!!!!!!!!"); + + for cid in &got { + dbg!(cid.to_string()); + } + + pretty::assert_eq!( + got, + vec![ + carol_to_dave.cid()?, + bob_to_carol.cid()?, + alice_to_bob.cid()? + ] + ); + + Ok(()) + } } } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index d19b5d13..c917dc20 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -15,7 +15,7 @@ use web_time::SystemTime; pub trait Store + Clone, C: Codec + TryFrom + Into> where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { @@ -91,7 +91,7 @@ impl< C: Codec + TryFrom + Into, > Store for &T where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 11c3de80..ee7e44d1 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -37,7 +37,7 @@ pub struct Agent< V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, > where - Delegation: Encode, + Ipld: Encode, delegation::Payload: TryFrom>, Named: From>, { @@ -70,7 +70,6 @@ where >::DelegationStoreError: fmt::Debug, delegation::Payload: TryFrom>, Named: From>, - Delegation: Encode, { pub fn new( did: DID, @@ -652,7 +651,7 @@ mod tests { // 4. [dnslink -d-> account -*-> server -a-> device] // 1. account -*-> server - let account_pbox = crate::Delegation::try_sign( + let account_to_server = crate::Delegation::try_sign( &account_signer, varsig_header.clone(), crate::delegation::PayloadBuilder::default() @@ -665,7 +664,7 @@ mod tests { )?; // 2. server -a-> device - let account_device_ucan = crate::Delegation::try_sign( + let server_to_device = crate::Delegation::try_sign( &server_signer, varsig_header.clone(), // FIXME can also put this on a builder crate::delegation::PayloadBuilder::default() @@ -678,7 +677,7 @@ mod tests { )?; // 3. dnslink -d-> account - let dnslink_ucan = crate::Delegation::try_sign( + let dnslink_to_account = crate::Delegation::try_sign( &dnslink_signer, varsig_header.clone(), crate::delegation::PayloadBuilder::default() @@ -690,9 +689,9 @@ mod tests { .build()?, )?; - del_store.insert(account_device_ucan.clone())?; - del_store.insert(account_pbox.clone())?; - del_store.insert(dnslink_ucan.clone())?; + del_store.insert(account_to_server.clone())?; + del_store.insert(server_to_device.clone())?; + del_store.insert(dnslink_to_account.clone())?; let proofs_for_powerline: Vec = del_store .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? @@ -722,6 +721,18 @@ mod tests { .build()?, )?; + dbg!("==================="); + dbg!(proofs_for_powerline.len()); + dbg!(">>>>>>>>>>>>>>>>>."); + dbg!(account_to_server.cid()?.to_string()); + dbg!(server_to_device.cid()?.to_string()); + dbg!(dnslink_to_account.cid()?.to_string()); + + dbg!("<<<<<<<<<<<<<<<<<<"); + for prf_cid in &proofs_for_powerline { + dbg!(prf_cid.to_string()); + } + let powerline_len = proofs_for_powerline.len(); let dnslink_len = chain_for_dnslink? .ok_or("failed while finding DNSLink delegtaions")? @@ -760,7 +771,7 @@ mod tests { ); let observed = agent.receive(ctx.account_invocation.clone()); - assert!(observed.is_ok()); + assert_matches!(observed, Ok(Recipient::You(_))); Ok(()) } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 4c9279fa..224e0c77 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,134 +1,7 @@ //! Storage for [`Invocation`]s. -use super::Invocation; -use crate::ability; -use crate::{crypto::varsig, did::Did}; -use libipld_core::{cid::Cid, codec::Codec}; -use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use std::{collections::BTreeMap, convert::Infallible}; +mod memory; +mod traits; -pub trait Store, C: Codec + Into + TryFrom> { - type InvocationStoreError; - - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError>; - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError>; - - fn has(&self, cid: Cid) -> Result { - Ok(self.get(cid).is_ok()) - } -} - -impl< - S: Store, - T, - DID: Did, - V: varsig::Header, - C: Codec + Into + TryFrom, - > Store for &S -{ - type InvocationStoreError = >::InvocationStoreError; - - fn get( - &self, - cid: Cid, - ) -> Result< - Option>>, - >::InvocationStoreError, - > { - (**self).get(cid) - } - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), >::InvocationStoreError> { - (**self).put(cid, invocation) - } -} - -#[derive(Debug, Clone)] -pub struct MemoryStore< - T = crate::ability::preset::Preset, - DID: crate::did::Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, -> { - inner: Arc>>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryStoreInner< - T = crate::ability::preset::Preset, - DID: crate::did::Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, -> { - store: BTreeMap>>, -} - -impl, Enc: Codec + Into + TryFrom> - MemoryStore -{ - fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { - match self.inner.read() { - Ok(guard) => guard, - Err(poison) => { - // There's no logic errors through lock poisoning in our case - poison.into_inner() - } - } - } - - fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { - match self.inner.write() { - Ok(guard) => guard, - Err(poison) => { - // There's no logic errors through lock poisoning in our case - poison.into_inner() - } - } - } -} - -impl, Enc: Codec + Into + TryFrom> Default - for MemoryStore -{ - fn default() -> Self { - Self { - inner: Arc::new(RwLock::new(MemoryStoreInner { - store: BTreeMap::new(), - })), - } - } -} - -impl, Enc: Codec + Into + TryFrom> - Store for MemoryStore -{ - type InvocationStoreError = Infallible; - - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError> { - Ok(self.read().store.get(&cid).cloned()) - } - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError> { - self.write().store.insert(cid, Arc::new(invocation)); - Ok(()) - } -} +pub use memory::{MemoryStore, MemoryStoreInner}; +pub use traits::Store; diff --git a/src/invocation/store/memory.rs b/src/invocation/store/memory.rs new file mode 100644 index 00000000..2ae9a360 --- /dev/null +++ b/src/invocation/store/memory.rs @@ -0,0 +1,83 @@ +use crate::{crypto::varsig, did::Did, invocation::Invocation}; +use super::Store; +use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::{collections::BTreeMap, convert::Infallible}; + +#[derive(Debug, Clone)] +pub struct MemoryStore< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + inner: Arc>>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStoreInner< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>>, +} + +impl, Enc: Codec + Into + TryFrom> + MemoryStore +{ + fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { + match self.inner.read() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } + + fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { + match self.inner.write() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } +} + +impl, Enc: Codec + Into + TryFrom> Default + for MemoryStore +{ + fn default() -> Self { + Self { + inner: Arc::new(RwLock::new(MemoryStoreInner { + store: BTreeMap::new(), + })), + } + } +} + +impl, Enc: Codec + Into + TryFrom> + Store for MemoryStore +{ + type InvocationStoreError = Infallible; + + fn get( + &self, + cid: Cid, + ) -> Result>>, Self::InvocationStoreError> { + Ok(self.read().store.get(&cid).cloned()) + } + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError> { + self.write().store.insert(cid, Arc::new(invocation)); + Ok(()) + } +} diff --git a/src/invocation/store/traits.rs b/src/invocation/store/traits.rs new file mode 100644 index 00000000..6d3fc723 --- /dev/null +++ b/src/invocation/store/traits.rs @@ -0,0 +1,51 @@ +use crate::{crypto::varsig, did::Did, invocation::Invocation}; +use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::Arc; + +pub trait Store, C: Codec + Into + TryFrom> { + type InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result>>, Self::InvocationStoreError>; + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError>; + + fn has(&self, cid: Cid) -> Result { + Ok(self.get(cid).is_ok()) + } +} + +impl< + S: Store, + T, + DID: Did, + V: varsig::Header, + C: Codec + Into + TryFrom, + > Store for &S +{ + type InvocationStoreError = >::InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result< + Option>>, + >::InvocationStoreError, + > { + (**self).get(cid) + } + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), >::InvocationStoreError> { + (**self).put(cid, invocation) + } +} From 279f17c67cd61bc2219fc67e8b63adb9f4e354cb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 24 Mar 2024 21:01:25 -0700 Subject: [PATCH 3/4] Save WIP writng more tests --- src/delegation/store/memory.rs | 33 ++++++------------------ src/invocation/agent.rs | 46 ++++++++-------------------------- 2 files changed, 18 insertions(+), 61 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 9dcf04b3..a0cd4ec1 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -198,6 +198,7 @@ where Ok(()) } + // FIXME take a PayloadBuilder fn get_chain( &self, aud: &DID, @@ -226,37 +227,20 @@ where format!("{}/", command) }; - // TODO Vec> - // parent_candidate_stack.push(sub_candidates.iter()); // .chain(powerline_candidates.iter())); - - // Pseudocode: - // If empty, pop: - // if pop fials, you're out of stuff - // If - 'outer: loop { - dbg!("OUTER"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { - dbg!("SOME INNER"); - for cid in parent_cid_candidates.clone() { - dbg!("INNER", cid.to_string()); - } if parent_cid_candidates.clone().collect::>().is_empty() { - dbg!("EMPTY"); parent_candidate_stack.pop(); continue; } 'inner: for cid in parent_cid_candidates { - dbg!("BBBBBBBBBBBBBBBBBBBBBB"); - dbg!(cid.to_string()); // CHECKS if read_tx.revocations.contains(cid) { continue; } if let Some(delegation) = read_tx.ucans.get(cid) { - dbg!("EEEEEEEEEEEEEEE"); if delegation.check_time(now).is_err() { continue; } @@ -292,7 +276,6 @@ where // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() { - dbg!("HHHHHHHHHHHH"); break 'outer; } @@ -852,7 +835,13 @@ mod tests { store.insert(alice_to_bob.clone())?; let got: Vec = store - .get_chain(&dave, &None, "/".into(), vec![], SystemTime::now()) + .get_chain( + &dave, + &Some(alice.clone()), + "/".into(), + vec![], + SystemTime::now(), + ) .map_err(|e| e.to_string())? .ok_or("failed during proof lookup")? .iter() @@ -860,12 +849,6 @@ mod tests { .cloned() .collect(); - dbg!("THERE!!!!!!!!!!!!!!!!!"); - - for cid in &got { - dbg!(cid.to_string()); - } - pretty::assert_eq!( got, vec![ diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index ee7e44d1..158f49c2 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -613,7 +613,6 @@ mod tests { struct Ctx { varsig_header: crate::crypto::varsig::header::Preset, - powerline_len: usize, dnslink_len: usize, inv_store: crate::invocation::store::MemoryStore, del_store: crate::delegation::store::MemoryStore, @@ -693,21 +692,19 @@ mod tests { del_store.insert(server_to_device.clone())?; del_store.insert(dnslink_to_account.clone())?; - let proofs_for_powerline: Vec = del_store - .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? + let chain_for_dnslink: Vec = del_store + .get_chain( + &device, + &Some(dnslink.clone()), + "/".into(), + vec![], + SystemTime::now(), + )? .ok_or("failed during proof lookup")? .iter() .map(|x| x.0.clone()) .collect(); - let chain_for_dnslink = del_store.get_chain( - &device, - &Some(dnslink.clone()), - "/".into(), - vec![], - SystemTime::now(), - ); - // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, @@ -717,30 +714,14 @@ mod tests { .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(proofs_for_powerline.clone()) + .proofs(chain_for_dnslink.clone()) .build()?, )?; - dbg!("==================="); - dbg!(proofs_for_powerline.len()); - dbg!(">>>>>>>>>>>>>>>>>."); - dbg!(account_to_server.cid()?.to_string()); - dbg!(server_to_device.cid()?.to_string()); - dbg!(dnslink_to_account.cid()?.to_string()); - - dbg!("<<<<<<<<<<<<<<<<<<"); - for prf_cid in &proofs_for_powerline { - dbg!(prf_cid.to_string()); - } - - let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink? - .ok_or("failed while finding DNSLink delegtaions")? - .len(); + let dnslink_len = chain_for_dnslink.len(); Ok(Ctx { varsig_header, - powerline_len, dnslink_len, inv_store, del_store, @@ -752,13 +733,6 @@ mod tests { }) } - #[test_log::test] - fn test_chain_len() -> TestResult { - let ctx = setup_test_chain()?; - assert_eq!((ctx.powerline_len, ctx.dnslink_len), (3, 3)); - Ok(()) - } - #[test_log::test] fn test_chain_ok() -> TestResult { let ctx = setup_test_chain()?; From 6affc01c5dd5ce5323c84f40a3d1dd7cc3c4f131 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 25 Mar 2024 15:41:35 -0700 Subject: [PATCH 4/4] Not pretty, but got that last selector passing --- .../delegation/policy/selector/select.txt | 7 + src/delegation/policy/predicate.rs | 1 - src/delegation/policy/selector.rs | 12 ++ src/delegation/policy/selector/select.rs | 152 ++++++++++++++---- 4 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 proptest-regressions/delegation/policy/selector/select.txt diff --git a/proptest-regressions/delegation/policy/selector/select.txt b/proptest-regressions/delegation/policy/selector/select.txt new file mode 100644 index 00000000..716e8c0c --- /dev/null +++ b/proptest-regressions/delegation/policy/selector/select.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 6496f8ae07f0fa0d57c9bc4d581551bc9940c50fe830880006156471a72e806b # shrinks to data = Newtype(null), more = [ArrayIndex(0)] diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 12cb7eae..30c9c0dd 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1132,7 +1132,6 @@ mod tests { #[test_log::test] fn test_eq_dot_field_inner_try_null() -> TestResult { - // FIXME double check against jq let p = Predicate::Equal(Select::from_str(".nope?.not").unwrap(), Ipld::Null.into()); assert!(p.run(&email())?); diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 28af8f2e..acba9a2c 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -200,6 +200,18 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_inner_try_is_null() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".nope?.not"), + Ok(Selector(vec![ + Filter::Try(Box::new(Filter::Field("nope".into()))), + Filter::Field("not".into()) + ])) + ); + Ok(()) + } + #[test_log::test] fn test_dot_many_tries_and_dot_field() -> TestResult { pretty::assert_eq!( diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 8be9fb08..7506cc63 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -46,9 +46,9 @@ impl Select { impl Select { pub fn get(self, ctx: &Ipld) -> Result { - self.filters - .iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + let got = self.filters.iter().try_fold( + (ctx.clone(), vec![], false), + |(ipld, mut seen_ops, is_try), op| { seen_ops.push(op); match op { @@ -57,70 +57,95 @@ impl Select { let ipld: Ipld = Select::::new(vec![op]).get(ctx).unwrap_or(Ipld::Null); - Ok((ipld, seen_ops)) + Ok((ipld, seen_ops.clone(), true)) } Filter::ArrayIndex(i) => { let result = { match ipld { Ipld::List(xs) => { if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, + return Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + ), )); }; xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, + .ok_or(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + ), )) .cloned() } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAList, + _ => Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + ), )), } }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } Filter::Field(k) => { let result = match ipld { Ipld::Map(xs) => xs .get(k) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::KeyNotFound, + .ok_or(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + ), )) .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, + _ => Err(( + is_try, + SelectorError::from_refs(&seen_ops, SelectorErrorReason::NotAMap), )), }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } Filter::Values => { let result = match ipld { Ipld::List(xs) => Ok(Ipld::List(xs)), Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, + _ => Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + ), )), }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) + }, + ); + + let (ipld, path) = match got { + Ok((ipld, seen_ops, _)) => Ok((ipld, seen_ops)), + Err((is_try, ref e @ SelectorError { ref selector, .. })) => { + if is_try { + Ok((Ipld::Null, selector.0.iter().map(|x| x).collect::>())) + } else { + Err(e.clone()) + } + } + }?; + + T::try_select(ipld).map_err(|e| SelectorError::from_refs(&path, e)) } } @@ -166,3 +191,70 @@ impl Arbitrary for Select { .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ipld; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod get { + use super::*; + + fn nested_data() -> Ipld { + Ipld::Map( + vec![ + ("name".to_string(), Ipld::String("Alice".to_string())), + ("age".to_string(), Ipld::Integer(42)), + ( + "friends".to_string(), + Ipld::List(vec![ + Ipld::String("Bob".to_string()), + Ipld::String("Charlie".to_string()), + ]), + ), + ] + .into_iter() + .collect(), + ) + } + + proptest! { + #[test_log::test] + fn test_identity(data: ipld::Newtype) { + let selector = Select::::from_str(".")?; + prop_assert_eq!(selector.get(&data.0)?, data); + } + + #[test_log::test] + fn test_try_missing_is_null(data: ipld::Newtype) { + let selector = Select::::from_str(".foo?")?; + let cleaned_data = match data.0.clone() { + Ipld::Map(mut m) => { + m.remove("foo").map_or(Ipld::Null, |v| v) + } + ipld => ipld + }; + prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); + } + + #[test_log::test] + fn test_try_missing_plus_trailing_is_null(data: ipld::Newtype, more: Vec) { + let mut filters = vec![Filter::Try(Box::new(Filter::Field("foo".into())))]; + filters.append(&mut more.clone()); + + let selector: Select = Select::new(filters); + + let cleaned_data = match data.0.clone() { + Ipld::Map(mut m) => { + m.remove("foo").map_or(Ipld::Null, |v| v) + } + ipld => ipld + }; + prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); + } + } + } +}