Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRAFT: Add StatusTracker to IdentityAssertion parsing and validation APIs #943

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
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
55 changes: 44 additions & 11 deletions cawg_identity/src/identity_assertion/assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
};

use c2pa::{Manifest, Reader};
use c2pa_status_tracker::{log_item, StatusTracker};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;

Expand Down Expand Up @@ -63,14 +64,31 @@
/// Iterator returns a [`Result`] because each assertion may fail to parse.
///
/// Aside from CBOR parsing, no further validation is performed.
pub fn from_manifest(
manifest: &Manifest,
) -> impl Iterator<Item = Result<Self, c2pa::Error>> + use<'_> {
pub fn from_manifest<'a>(
manifest: &'a Manifest,
status_tracker: &'a mut StatusTracker,
) -> impl Iterator<Item = Result<Self, c2pa::Error>> + use<'a> {
manifest
.assertions()
.iter()
.filter(|a| a.label().starts_with("cawg.identity"))
.map(|a| a.to_assertion())
.map(|a| (a.label().to_owned(), a.to_assertion()))
.inspect(|(label, r)| {
if let Err(err) = r {
// TO DO: a.label() is probably wrong (not a full JUMBF URI)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gpeacock is there a way I can get from Manifest.assertions() iterator to the JUMBF URI for the assertion returned?

log_item!(
label.clone(),
"invalid CBOR",
"IdentityAssertion::from_manifest"
)
.validation_status("cawg.identity.cbor.invalid")
.failure_no_throw(
status_tracker,
c2pa::Error::AssertionSpecificError(err.to_string()),
);
}
})
.map(move |(_label, r)| r)
}

/// Create a summary report from this `IdentityAssertion`.
Expand All @@ -83,25 +101,28 @@
pub async fn to_summary<SV: SignatureVerifier>(
&self,
manifest: &Manifest,
status_tracker: &mut StatusTracker,

Check warning on line 104 in cawg_identity/src/identity_assertion/assertion.rs

View check run for this annotation

Codecov / codecov/patch

cawg_identity/src/identity_assertion/assertion.rs#L104

Added line #L104 was not covered by tests
verifier: &SV,
) -> impl Serialize
where
<SV as SignatureVerifier>::Output: 'static,
{
self.to_summary_impl(manifest, verifier).await
self.to_summary_impl(manifest, status_tracker, verifier)
.await

Check warning on line 111 in cawg_identity/src/identity_assertion/assertion.rs

View check run for this annotation

Codecov / codecov/patch

cawg_identity/src/identity_assertion/assertion.rs#L110-L111

Added lines #L110 - L111 were not covered by tests
}

pub(crate) async fn to_summary_impl<SV: SignatureVerifier>(
&self,
manifest: &Manifest,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> IdentityAssertionReport<
<<SV as SignatureVerifier>::Output as ToCredentialSummary>::CredentialSummary,
>
where
<SV as SignatureVerifier>::Output: 'static,
{
match self.validate(manifest, verifier).await {
match self.validate(manifest, status_tracker, verifier).await {
Ok(named_actor) => {
let summary = named_actor.to_summary();

Expand All @@ -120,13 +141,15 @@
/// Summarize all of the identity assertions found for a [`Manifest`].
pub async fn summarize_all<SV: SignatureVerifier>(
manifest: &Manifest,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> impl Serialize {
Self::summarize_all_impl(manifest, verifier).await
Self::summarize_all_impl(manifest, status_tracker, verifier).await
}

pub(crate) async fn summarize_all_impl<SV: SignatureVerifier>(
manifest: &Manifest,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> IdentityAssertionsForManifest<
<<SV as SignatureVerifier>::Output as ToCredentialSummary>::CredentialSummary,
Expand All @@ -139,9 +162,16 @@
>,
> = vec![];

for assertion in Self::from_manifest(manifest) {
let assertion_results: Vec<Result<IdentityAssertion, c2pa::Error>> =
Self::from_manifest(manifest, status_tracker).collect();

for assertion in assertion_results {
let report = match assertion {
Ok(assertion) => assertion.to_summary_impl(manifest, verifier).await,
Ok(assertion) => {
assertion
.to_summary_impl(manifest, status_tracker, verifier)
.await
}
Err(_) => {
todo!("Handle assertion failed to parse case");
}
Expand All @@ -163,6 +193,7 @@
#[cfg(feature = "v1_api")]
pub async fn summarize_manifest_store<SV: SignatureVerifier>(
store: &c2pa::ManifestStore,
status_tracker: &mut StatusTracker,

Check warning on line 196 in cawg_identity/src/identity_assertion/assertion.rs

View check run for this annotation

Codecov / codecov/patch

cawg_identity/src/identity_assertion/assertion.rs#L196

Added line #L196 was not covered by tests
verifier: &SV,
) -> impl Serialize {
// NOTE: We can't write this using .map(...).collect() because there are async
Expand All @@ -175,7 +206,7 @@
> = BTreeMap::new();

for (id, manifest) in store.manifests() {
let report = Self::summarize_all_impl(manifest, verifier).await;
let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await;

Check warning on line 209 in cawg_identity/src/identity_assertion/assertion.rs

View check run for this annotation

Codecov / codecov/patch

cawg_identity/src/identity_assertion/assertion.rs#L209

Added line #L209 was not covered by tests
reports.insert(id.clone(), report);
}

Expand All @@ -189,6 +220,7 @@
/// Summarize all of the identity assertions found for a [`Reader`].
pub async fn summarize_from_reader<SV: SignatureVerifier>(
reader: &Reader,
status_tracker: &mut StatusTracker,
verifier: &SV,
) -> impl Serialize {
// NOTE: We can't write this using .map(...).collect() because there are async
Expand All @@ -201,7 +233,7 @@
> = BTreeMap::new();

for (id, manifest) in reader.manifests() {
let report = Self::summarize_all_impl(manifest, verifier).await;
let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await;
reports.insert(id.clone(), report);
}

Expand All @@ -222,6 +254,7 @@
pub async fn validate<SV: SignatureVerifier>(
&self,
manifest: &Manifest,
_status_tracker: &mut StatusTracker,
verifier: &SV,
) -> Result<SV::Output, ValidationError<SV::Error>> {
self.check_padding()?;
Expand Down
73 changes: 13 additions & 60 deletions cawg_identity/src/tests/builder/simple_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use std::io::{Cursor, Seek};

use c2pa::{Builder, Reader, SigningAlg};
use serde_json::json;
use c2pa_status_tracker::StatusTracker;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::wasm_bindgen_test;

Expand All @@ -23,7 +23,10 @@ use crate::{
AsyncIdentityAssertionBuilder, AsyncIdentityAssertionSigner, IdentityAssertionBuilder,
IdentityAssertionSigner,
},
tests::fixtures::{NaiveAsyncCredentialHolder, NaiveCredentialHolder, NaiveSignatureVerifier},
tests::fixtures::{
manifest_json, parent_json, NaiveAsyncCredentialHolder, NaiveCredentialHolder,
NaiveSignatureVerifier,
},
IdentityAssertion, ToCredentialSummary,
};

Expand Down Expand Up @@ -70,17 +73,17 @@ async fn simple_case() {
assert_eq!(manifest_store.validation_status(), None);

let manifest = manifest_store.active_manifest().unwrap();
let mut ia_iter = IdentityAssertion::from_manifest(manifest);
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);

// Should find exactly one identity assertion.
let ia = ia_iter.next().unwrap().unwrap();
dbg!(&ia);

assert!(ia_iter.next().is_none());
drop(ia_iter);

// And that identity assertion should be valid for this manifest.
let nsv = NaiveSignatureVerifier {};
let naive_credential = ia.validate(manifest, &nsv).await.unwrap();
let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap();

let nc_summary = naive_credential.to_summary();
let nc_json = serde_json::to_string(&nc_summary).unwrap();
Expand Down Expand Up @@ -125,69 +128,19 @@ async fn simple_case_async() {
assert_eq!(manifest_store.validation_status(), None);

let manifest = manifest_store.active_manifest().unwrap();
let mut ia_iter = IdentityAssertion::from_manifest(manifest);
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);

// Should find exactly one identity assertion.
let ia = ia_iter.next().unwrap().unwrap();
dbg!(&ia);

assert!(ia_iter.next().is_none());
drop(ia_iter);

// And that identity assertion should be valid for this manifest.
let nsv = NaiveSignatureVerifier {};
let naive_credential = ia.validate(manifest, &nsv).await.unwrap();
let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap();

let nc_summary = naive_credential.to_summary();
let nc_json = serde_json::to_string(&nc_summary).unwrap();
assert_eq!(nc_json, "{}");
}

fn manifest_json() -> String {
json!({
"vendor": "test",
"claim_generator_info": [
{
"name": "c2pa_test",
"version": "1.0.0"
}
],
"metadata": [
{
"dateTime": "1985-04-12T23:20:50.52Z",
"my_custom_metadata": "my custom metatdata value"
}
],
"title": "Test_Manifest",
"format": "image/tiff",
"instance_id": "1234",
"thumbnail": {
"format": "image/jpeg",
"identifier": "thumbnail.jpg"
},
"ingredients": [
{
"title": "Test",
"format": "image/jpeg",
"instance_id": "12345",
"relationship": "componentOf"
}
],
"assertions": [
{
"label": "org.test.assertion",
"data": "assertion"
}
]
})
.to_string()
}

fn parent_json() -> String {
json!({
"title": "Parent Test",
"format": "image/jpeg",
"instance_id": "12345",
"relationship": "parentOf"
})
.to_string()
}
16 changes: 11 additions & 5 deletions cawg_identity/src/tests/claim_aggregation/interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use std::{io::Cursor, str::FromStr};

use c2pa::{HashedUri, Reader};
use c2pa_status_tracker::StatusTracker;
use chrono::{DateTime, FixedOffset};
use iref::UriBuf;
use non_empty_string::NonEmptyString;
Expand Down Expand Up @@ -41,15 +42,17 @@ async fn adobe_connected_identities() {
assert_eq!(reader.validation_status(), None);

let manifest = reader.active_manifest().unwrap();
let mut ia_iter = IdentityAssertion::from_manifest(manifest);
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);

// Should find exactly one identity assertion.
let ia = ia_iter.next().unwrap().unwrap();
assert!(ia_iter.next().is_none());
drop(ia_iter);

// And that identity assertion should be valid for this manifest.
let isv = IcaSignatureVerifier {};
let ica = ia.validate(manifest, &isv).await.unwrap();
let ica = ia.validate(manifest, &mut st, &isv).await.unwrap();

// There should be exactly one verified identity.
let ica_vc = ica.credential_subjects.first();
Expand Down Expand Up @@ -87,7 +90,8 @@ async fn adobe_connected_identities() {
);

// Check the summary report for the entire manifest store.
let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &isv).await;
let mut st = StatusTracker::default();
let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &mut st, &isv).await;
let ia_json = serde_json::to_string(&ia_summary).unwrap();

assert_eq!(
Expand All @@ -96,7 +100,8 @@ async fn adobe_connected_identities() {
);

// Check the summary report for this manifest.
let ia_summary = IdentityAssertion::summarize_all(manifest, &isv).await;
let mut st = StatusTracker::default();
let ia_summary = IdentityAssertion::summarize_all(manifest, &mut st, &isv).await;
let ia_json = serde_json::to_string(&ia_summary).unwrap();

assert_eq!(
Expand All @@ -121,8 +126,9 @@ async fn ims_multiple_manifests() {
assert_eq!(reader.validation_status(), None);

// Check the summary report for the entire manifest store.
let mut st = StatusTracker::default();
let isv = IcaSignatureVerifier {};
let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &isv).await;
let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &mut st, &isv).await;
let ia_json = serde_json::to_string(&ia_summary).unwrap();

assert_eq!(
Expand Down
65 changes: 65 additions & 0 deletions cawg_identity/src/tests/fixtures/manifest_json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2025 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use serde_json::json;

/// Boilerplate JSON to use when building manifests for test cases.
pub(crate) fn manifest_json() -> String {
json!({
"vendor": "test",
"claim_generator_info": [
{
"name": "c2pa_test",
"version": "1.0.0"
}
],
"metadata": [
{
"dateTime": "1985-04-12T23:20:50.52Z",
"my_custom_metadata": "my custom metatdata value"
}
],
"title": "Test_Manifest",
"format": "image/tiff",
"instance_id": "1234",
"thumbnail": {
"format": "image/jpeg",
"identifier": "thumbnail.jpg"
},
"ingredients": [
{
"title": "Test",
"format": "image/jpeg",
"instance_id": "12345",
"relationship": "componentOf"
}
],
"assertions": [
{
"label": "org.test.assertion",
"data": "assertion"
}
]
})
.to_string()
}

pub(crate) fn parent_json() -> String {
json!({
"title": "Parent Test",
"format": "image/jpeg",
"instance_id": "12345",
"relationship": "parentOf"
})
.to_string()
}
Loading
Loading