Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions keyhive_core/src/keyhive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,21 @@ where
Ok(true)
}

/// Whether every certificate `op` cites is in `doc`'s closure.
async fn cites_only_closure(
&self,
op: &CgkaOperation,
doc: Identifier,
) -> Result<bool, KeylineStorageError<K::Error>> {
let closure: BTreeSet<CitationId> = self
.closure(doc)
.await?
.iter()
.map(|cert| citation_of(cert.id()))
.collect();
Ok(op.citations().iter().all(|c| closure.contains(c)))
}

/// The certificate `citation` refers to, if the Keyline store has it.
async fn stored_certificate(
&self,
Expand Down Expand Up @@ -2749,8 +2764,9 @@ where
/// Denied if a predecessor was already denied. Otherwise pending until every
/// predecessor is in the CGKA graph, then denied if its citations are not
/// in strictly ascending order, and otherwise pending until every cited
/// certificate is recorded. After that the verdict depends only on the
/// operation and its causal past, so it is final and deterministic.
/// certificate is recorded and in the document's closure. After that the
/// verdict depends only on the operation and its causal past, so it is
/// final and deterministic.
///
/// Conditions:
/// * Its signer has `Read` or higher in its signing set (i.e., the predecessor
Expand Down Expand Up @@ -2790,7 +2806,7 @@ where
};

let cited = op.citations();
if !self.record_citations(op).await? {
if !self.record_citations(op).await? || !self.cites_only_closure(op, doc).await? {
return Ok(Err(Pending));
}

Expand Down
17 changes: 17 additions & 0 deletions keyhive_core/tests/cgka_authz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1436,3 +1436,20 @@ async fn a_group_admin_can_remove_a_member_from_the_groups_documents() -> TestRe
);
Ok(())
}

#[tokio::test]
async fn an_operation_citing_a_certificate_outside_its_documents_closure_is_pending() -> TestResult
{
let mut ctx = TestContext::new().await;
let alice = ctx.individual("alice").await?;
let mallory = ctx.individual("mallory").await?;
let doc = ctx.doc(&alice, "doc").await?;
let other = ctx.doc(&alice, "other").await?;
alice.add_member(mallory.id(), doc, Power::Read).await?;
let elsewhere = delegation_citation(&alice, kl(other), kl(alice.id())).await?;

let op = remove_op(&alice, doc, BTreeSet::from([elsewhere])).await;
let result = alice.receive_cgka_op(mallory.sign_cgka_op(op).await?).await;
assert!(is_pending(&result), "{result:?}");
Ok(())
}
Loading