use std::time::Duration; use credo::{ ClaimBody, Credential, CredentialSource, Credo, PermissionKind, SimpleCredentialSource, WRITE_TO_SCOPE, }; use futures::{ future::{self}, FutureExt, StreamExt, }; use litl::json; use mofo::Mofo; use caro::Remote; use tracing::trace; macro_rules! timeout { ($f:expr) => { tokio::time::timeout(Duration::from_millis(500), $f).map(|r| r.expect("Timed out")) }; } #[tokio::test] async fn can_create_child_scopes() { // traceful::init_test(); let background = Mofo::new(); let credo = Credo::new("credo".to_string(), background.clone()); background .run_until(async { let initial_recipient = Credential::new_random(); let credential_source = Box::new(SimpleCredentialSource::new()); let scope = credo .create_scope( initial_recipient.clone(), vec![PermissionKind::MakeStatement { path_prefix: "test".to_string(), }], None, credential_source.clone_ref(), ) .await; let updates = scope.updates("test".to_owned()); let mut updates = updates.skip_while(|s| future::ready(s.valid_claims.len() < 8)); assert_eq!( timeout!(updates.next()).await.unwrap().valid_claims.len(), 8 ); let child_scope = credo .create_scope( initial_recipient.clone(), vec![PermissionKind::InheritFrom], Some(scope.id()), credential_source.clone_ref(), ) .await; let child_updates = child_scope.updates("test-child".to_owned()); let mut child_updates = child_updates.skip_while(|s| future::ready(s.valid_claims.len() < 18)); let child_valid_claims = timeout!(child_updates.next()).await.unwrap().valid_claims; assert_eq!(child_valid_claims.len(), 18); let child_claim_id = child_scope .make_claim_after( ClaimBody::Statement { path: "test".to_string(), value: json!("hello world").into(), }, vec![], ) .await .unwrap(); let child_valid_claims = timeout!(child_updates.next()).await.unwrap().valid_claims; assert_eq!(child_valid_claims.len(), 19); assert!(child_valid_claims.contains_key(&child_claim_id)); assert_eq!( child_valid_claims.get(&child_claim_id).unwrap().0.body, ClaimBody::Statement { path: "test".to_string(), value: json!("hello world").into(), } ); }) .await; } // TODO: this isn't nearly strict enough, just move the revocation test at the end to the next (proper, two-node) test #[tokio::test] async fn child_scopes_get_notified_and_influenced_by_parent_changes() { // traceful::init_test(); let background = Mofo::new(); let credo = Credo::new("credo".to_string(), background.clone()); background .run_until(async { let initial_recipient = Credential::new_random(); let credential_source = Box::new(SimpleCredentialSource::new()); let parent_scope = credo .create_scope( initial_recipient.clone(), vec![PermissionKind::Delegate { delegated: Box::new(PermissionKind::MakeStatement { path_prefix: "test".to_string(), }), }], None, credential_source.clone_ref(), ) .await; let updates = parent_scope.updates("test".to_owned()); let mut updates = updates.skip_while(|s| future::ready(s.valid_claims.len() < 8)); assert_eq!( timeout!(updates.next()).await.unwrap().valid_claims.len(), 8 ); let second_recipient = Credential::new_random(); let permission_id = parent_scope .make_claim_after( ClaimBody::Permission { permitted: PermissionKind::MakeStatement { path_prefix: "test".to_string(), }, to: second_recipient.for_making_claims.pub_id(), as_of: ti64::now(), }, vec![], ) .await .unwrap(); parent_scope .make_claim(ClaimBody::AddSharedSecretRecipient { secret_kind: WRITE_TO_SCOPE.to_owned(), recipient: second_recipient.for_accepting_secrets.pub_id(), }) .await .unwrap(); parent_scope .re_reveal_shared_secret(WRITE_TO_SCOPE) .await .unwrap(); // we create the child scope with the initial recipient to make sure that the second recipient // inherits permissions from the parent scope let child_scope_by_initial = credo .create_scope( initial_recipient.clone(), vec![PermissionKind::InheritFrom], Some(parent_scope.id()), credential_source, ) .await; let new_credential_source = Box::new(SimpleCredentialSource::new()); new_credential_source .add_credential(parent_scope.id(), second_recipient) .await; let child_scope = credo.get_scope(&child_scope_by_initial.id(), new_credential_source); let child_updates = child_scope.updates("test-child".to_owned()); let mut child_updates = child_updates.skip_while(|s| future::ready(s.valid_claims.len() < 21)); let child_valid_claims = timeout!(child_updates.next()).await.unwrap().valid_claims; assert_eq!(child_valid_claims.len(), 21); let child_claim_id = child_scope .make_claim_after( ClaimBody::Statement { path: "test".to_string(), value: json!("hello world").into(), }, vec![], ) .await .unwrap(); let mut child_updates = child_updates.skip_while(|s| future::ready(s.valid_claims.len() < 22)); let child_valid_claims = timeout!(child_updates.next()).await.unwrap().valid_claims; assert_eq!(child_valid_claims.len(), 22); assert!(child_valid_claims.contains_key(&child_claim_id)); assert_eq!( child_valid_claims.get(&child_claim_id).unwrap().0.body, ClaimBody::Statement { path: "test".to_string(), value: json!("hello world").into(), } ); let revocation_id = parent_scope .make_claim_after( ClaimBody::Revocation { revoked_claim_id: permission_id, as_of: ti64::now(), }, vec![], ) .await .unwrap(); let child_valid_claims = timeout!(child_updates.next()).await.unwrap().valid_claims; assert_eq!(child_valid_claims.len(), 21); assert!(!child_valid_claims.contains_key(&child_claim_id)); assert!(child_valid_claims.contains_key(&revocation_id)); }) .await; } // this is similar to the previous test but intentionally split across two nodes to ensure credentials still behave correctly #[tokio::test] async fn child_scopes_can_be_used_from_other_nodes_with_invited_credentials() { // traceful::init_test_trace(); let background = Mofo::new(); let credo1 = Credo::new("credo1".to_string(), background.clone()); let credo2 = Credo::new("credo2".to_string(), background.clone()); background .run_until(async { let (credo1_as_remote, credo2_as_remote) = Remote::new_connected_test_pair("credo1", "credo2"); credo1.add_remote(credo2_as_remote).await; credo2.add_remote(credo1_as_remote).await; let initial_recipient = Credential::new_random(); let credential_source = Box::new(SimpleCredentialSource::new()); let scope = credo1 .create_scope( initial_recipient.clone(), vec![PermissionKind::Delegate { delegated: Box::new(PermissionKind::MakeStatement { path_prefix: "test".to_string(), }), }], None, credential_source.clone_ref(), ) .await; let updates = scope.updates("test".to_owned()); let mut updates = updates.skip_while(|s| future::ready(s.valid_claims.len() < 8)); assert_eq!( timeout!(updates.next()).await.unwrap().valid_claims.len(), 8 ); let second_recipient = Credential::new_random(); let permission_id = scope .make_claim(ClaimBody::Permission { permitted: PermissionKind::MakeStatement { path_prefix: "test".to_string(), }, to: second_recipient.for_making_claims.pub_id(), as_of: ti64::now(), }) .await .unwrap(); let allow_writing_id = scope .make_claim(ClaimBody::AddSharedSecretRecipient { secret_kind: WRITE_TO_SCOPE.to_string(), recipient: second_recipient.for_accepting_secrets.pub_id(), }) .await .unwrap(); scope.re_reveal_shared_secret(WRITE_TO_SCOPE).await.unwrap(); let expected_write_key_id_from_parent = scope .current_shared_secrets_for(WRITE_TO_SCOPE) .await .unwrap() .get(&scope.id()) .as_ref() .unwrap() .as_ref() .unwrap() .id; // we create the child scope with the initial recipient to make sure that the second recipient // inherits permissions from the parent scope let child_scope = credo1 .create_scope( initial_recipient.clone(), vec![PermissionKind::InheritFrom], Some(scope.id()), credential_source.clone_ref(), ) .await; let write_keys_id_in_child = child_scope .current_shared_secrets_for(WRITE_TO_SCOPE) .await .unwrap(); assert_eq!( write_keys_id_in_child .get(&scope.id()) .as_ref() .unwrap() .as_ref() .unwrap() .id, expected_write_key_id_from_parent ); let credential_source2 = Box::new(SimpleCredentialSource::new()); credential_source2 .add_credential(scope.id(), second_recipient.clone()) .await; let child_scope_2 = credo2.get_scope(&child_scope.id(), credential_source2); let child_updates = child_scope_2.updates("test-child".to_owned()); let mut child_updates = child_updates.skip_while(|s| { trace!("Got {} valid claims", s.valid_claims.len()); future::ready(s.valid_claims.len() < 20) }); let child_state = timeout!(child_updates.next()).await.unwrap(); trace!(child_state = ?child_state, "Got newest child state"); assert_eq!(child_state.valid_claims.len(), 21); assert!(child_state.valid_claims.contains_key(&permission_id)); assert!(child_state.valid_claims.contains_key(&allow_writing_id)); assert!(child_state .secret_recipients .get(WRITE_TO_SCOPE) .unwrap() .contains(&second_recipient.for_accepting_secrets.pub_id())); assert!(child_state.valid_claims.iter().any( |(_, (claim, _))| matches!(&claim.body, ClaimBody::EntrustToSharedSecret { secret_kind, for_key_id, .. } if secret_kind == WRITE_TO_SCOPE && for_key_id == &expected_write_key_id_from_parent) )); assert!(child_state.valid_claims.iter().any( |(_, (claim, _))| matches!(&claim.body, ClaimBody::RevealSharedSecret { secret_kind, key_id, encrypted_per_recipient, } if secret_kind == WRITE_TO_SCOPE && key_id == &expected_write_key_id_from_parent && encrypted_per_recipient .contains_key(&second_recipient.for_accepting_secrets.pub_id())) )); let child_claim_id = child_scope_2 .make_claim(ClaimBody::Statement { path: "test".to_string(), value: json!("hello world").into(), }) .await .unwrap(); let mut child_updates = child_updates.skip_while(|s| future::ready(s.valid_claims.len() < 22)); let child_valid_claims = timeout!(child_updates.next()).await.unwrap().valid_claims; assert_eq!(child_valid_claims.len(), 22); assert!(child_valid_claims.contains_key(&child_claim_id)); assert_eq!( child_valid_claims.get(&child_claim_id).unwrap().0.body, ClaimBody::Statement { path: "test".to_string(), value: json!("hello world").into(), } ); }) .await; } // TODO: #59 add test that checks revocation to secret access, proper current recipient set etc. for child scope