Skip to content

Commit

Permalink
feat(dataverse): support claim with named node hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
amimart committed Mar 26, 2024
1 parent 310dd5a commit 2081625
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 19 deletions.
67 changes: 49 additions & 18 deletions contracts/okp4-dataverse/src/registrar/rdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ impl<'a> DataverseCredential<'a> {

let claim_node = BlankNode { id: "c0" };
// Used to rename all blank nodes to avoid conflict with the forged claim node `c0`
let mut id_issuer = IdentifierIssuer::new("b", 0u128);
let triples: Vec<Triple<'_>> = self.as_triples(claim_node, &mut id_issuer)?;
let mut blank_issuer = IdentifierIssuer::new("b", 0u128);
// Used to replace named node based hierarchy with blank nodes
let mut named_issuer = IdentifierIssuer::new("a", 0u128);
let triples: Vec<Triple<'_>> =
self.as_triples(claim_node, &mut named_issuer, &mut blank_issuer)?;
let out: Vec<u8> = Vec::default();
let mut writer = TripleWriter::new(&format, out);
for triple in triples {
Expand All @@ -74,7 +77,8 @@ impl<'a> DataverseCredential<'a> {
fn as_triples(
&'a self,
claim_node: BlankNode<'a>,
id_issuer: &'a mut IdentifierIssuer,
named_issuer: &'a mut IdentifierIssuer,
blank_issuer: &'a mut IdentifierIssuer,
) -> Result<Vec<Triple<'a>>, ContractError> {
let c_subject = Subject::NamedNode(NamedNode { iri: self.id });

Expand Down Expand Up @@ -111,7 +115,7 @@ impl<'a> DataverseCredential<'a> {
},
];

triples.extend(self.claim_as_triples(claim_node, id_issuer)?);
triples.extend(self.claim_as_triples(claim_node, named_issuer, blank_issuer)?);

if let Some(valid_until) = self.valid_until {
triples.push(Triple {
Expand All @@ -130,15 +134,23 @@ impl<'a> DataverseCredential<'a> {
fn claim_as_triples(
&'a self,
claim_node: BlankNode<'a>,
id_issuer: &'a mut IdentifierIssuer,
named_issuer: &'a mut IdentifierIssuer,
blank_issuer: &'a mut IdentifierIssuer,
) -> Result<Vec<Triple<'a>>, ContractError> {
// issue replacement identifiers for blank nodes
// issue replacement identifiers for nodes
self.claim.content.iter().for_each(|q| {
if let Subject::BlankNode(BlankNode { id }) = q.subject {
let _ = id_issuer.get_or_issue(id.to_string());
}
match q.subject {
Subject::NamedNode(NamedNode { iri }) if iri != self.claim.id => {
named_issuer.get_or_issue(iri.to_string());
}
Subject::BlankNode(BlankNode { id }) => {
blank_issuer.get_or_issue(id.to_string());
}
_ => (),
};

if let Term::BlankNode(BlankNode { id }) = q.object {
let _ = id_issuer.get_or_issue(id.to_string());
blank_issuer.get_or_issue(id.to_string());
}
});

Expand All @@ -148,16 +160,21 @@ impl<'a> DataverseCredential<'a> {
.iter()
.map(|q| {
let subject = match q.subject {
Subject::NamedNode(n) => {
if n.iri != self.claim.id {
Err(ContractError::UnsupportedCredential(
"claim hierarchy can be forge only through blank nodes".to_string(),
))?;
}
Subject::NamedNode(n) if n.iri == self.claim.id => {
Subject::BlankNode(claim_node)
}
Subject::NamedNode(n) if n.iri != self.claim.id => {
Subject::BlankNode(BlankNode {
id: named_issuer.get(n.iri).ok_or_else(|| {
ContractError::Unexpected(
"Could not replace named node, canonical identifier not found"
.to_string(),
)

Check warning on line 172 in contracts/okp4-dataverse/src/registrar/rdf.rs

View check run for this annotation

Codecov / codecov/patch

contracts/okp4-dataverse/src/registrar/rdf.rs#L169-L172

Added lines #L169 - L172 were not covered by tests
})?,
})
}
Subject::BlankNode(BlankNode { id }) => Subject::BlankNode(BlankNode {
id: id_issuer.get(id).ok_or_else(|| {
id: blank_issuer.get(id).ok_or_else(|| {
ContractError::Unexpected(
"Could not replace blank node, canonical identifier not found"
.to_string(),
Expand All @@ -167,8 +184,12 @@ impl<'a> DataverseCredential<'a> {
_ => q.subject,
};
let object = match q.object {
Term::NamedNode(n) => match named_issuer.get(n.iri) {
Some(id) => Term::BlankNode(BlankNode { id }),
None => Term::NamedNode(n),
},
Term::BlankNode(BlankNode { id }) => Term::BlankNode(BlankNode {
id: id_issuer.get(id).ok_or_else(|| {
id: blank_issuer.get(id).ok_or_else(|| {
ContractError::Unexpected(
"Could not replace blank node, canonical identifier not found"
.to_string(),
Expand All @@ -186,6 +207,16 @@ impl<'a> DataverseCredential<'a> {
})
.collect::<Result<Vec<Triple<'a>>, ContractError>>()?;

named_issuer
.issued_iter()
.for_each(|(original, (_, replacement))| {
triples.push(Triple {
subject: Subject::BlankNode(BlankNode { id: replacement }),
predicate: VC_CLAIM_ORIGINAL_NODE,
object: Term::NamedNode(NamedNode { iri: original }),
});
});

triples.push(Triple {
subject: Subject::NamedNode(NamedNode { iri: self.id }),
predicate: VC_CLAIM,
Expand Down
6 changes: 5 additions & 1 deletion packages/okp4-rdf/src/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use itertools::Itertools;
use rio_api::model::{BlankNode, GraphName, Quad, Subject, Term};
use sha2;
use sha2::Digest;
use std::collections::hash_map::Entry;
use std::collections::hash_map::{Entry, Iter};
use std::collections::{BTreeMap, HashMap};
use thiserror::Error;

Expand Down Expand Up @@ -377,6 +377,10 @@ impl IdentifierIssuer {
pub fn issued(&self, identifier: &str) -> bool {
self.issued.contains_key(identifier)
}

pub fn issued_iter(&self) -> Iter<'_, String, (u128, String)> {
self.issued.iter()
}
}

trait WithBlankNodes<'a> {
Expand Down

0 comments on commit 2081625

Please sign in to comment.