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() {
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;
}
#[tokio::test]
async fn child_scopes_get_notified_and_influenced_by_parent_changes() {
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();
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;
}
#[tokio::test]
async fn child_scopes_can_be_used_from_other_nodes_with_invited_credentials() {
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;
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;
}